How to generate initialization of class fields with Roslyn - c#

I know how to create a local variable inside a method, for example this:
LocalDeclarationStatement(VariableDeclaration(IdentifierName("MyClass"))
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier("nameOfvariable"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword)))))));
would give me:
MyClass nameOfvariable = new MyClass();
But say that I already created a field and now I simply want to initialize it (in a method, constructor or anything) like this:
nameOfVariable = new MyClass();
How do I do this? My guess it have to do with the VariableDeclerator but I can't find a way to get it right so I can add it to a list that contains StatementSyntaxes. I can change the VariableDecleration to "VariableDeclaration(IdentifierName(""))" too but that gives me an ugly extra space infront of the statement.
It seems like I struggle with some really basic stuff of Roslyn and I try to check http://roslynquoter.azurewebsites.net/ but that feels like the forced way to do it (feels like it create a lot more code than necessary).
Update: Should clarify that I know how to create method/constructors. I'm only looking for a way to initialize a field when I only have access to the field name and field type. So the only code I want to generate is this:
myField = new MyField();

Well you're almost there, you just need to create all that. This should do what you're interested in:
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var declaration = local.Declaration;
var declarator = declaration.Variables.First();
var identifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(identifier, SyntaxFactory.ArgumentList(), null);
var equalsValueClause = SyntaxFactory.EqualsValueClause(objectCreationExpression);
var newDeclarator = declarator.WithInitializer(equalsValueClause).WithAdditionalAnnotations(Formatter.Annotation);
var newRoot = root.ReplaceNode(declarator, newDeclarator);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
Some explanation: you create a new identifier MyClass which will be used in your ObjectCreationExpression. Then you wrap all that in an EqualsValueClause and you set that as an initializer to your declarator. We also add the Formatter annotation to this node so we can format it later and don't end up with whitespace issues.
All that's left then is replacing the node in your original tree, formatting it and you're done:
-------------------------------------------------------------------------------
If you instead mean that you want to put the assignment on its own separately from the declaration then you have to create a new AssignmentExpression and wrap it inside a ExpressionStatement. Typically expressions and statements are distinct concepts but this ExpressionStatement allows us to treat an expression as a statement which is important because a method's body only accepts statements.
In code, it looks like this:
internal static void Execute()
{
const string source = #"
using System;
class MyClass
{
void Method()
{
MyClass nameOfVariable, another;
}
}
";
var tree = CSharpSyntaxTree.ParseText(source);
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree }, new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var semanticModel = compilation.GetSemanticModel(tree);
var root = tree.GetRoot();
var local = root.DescendantNodes().OfType<LocalDeclarationStatementSyntax>().First();
var method = local.Ancestors().OfType<MethodDeclarationSyntax>().First();
var variableIdentifier = SyntaxFactory.IdentifierName("nameOfVariable");
var classIdentifier = SyntaxFactory.IdentifierName("MyClass");
var objectCreationExpression = SyntaxFactory.ObjectCreationExpression(classIdentifier, SyntaxFactory.ArgumentList(), null);
var assignment = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, variableIdentifier, objectCreationExpression);
var expressionStatement = SyntaxFactory.ExpressionStatement(assignment).WithAdditionalAnnotations(Formatter.Annotation);
var newMethod = method.AddBodyStatements(expressionStatement);
var newRoot = root.ReplaceNode(method.Body, newMethod.Body);
var formattedRoot = Formatter.Format(newRoot, Formatter.Annotation, new AdhocWorkspace());
Console.WriteLine(formattedRoot.GetText());
Console.Read();
}
Result:

After some more trying and looking I found the answer. There is something called "AssignmentExpression" that you can use.
Here is an example how to use it:
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("myField"),
ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
.WithNewKeyword(Token(SyntaxKind.NewKeyword))));
This would give you:
myField = new Myclass();
So now it's easy to seperate creation and assignment/initialization to two different statements.
Note that I'm using "using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;" so I don't have to write SyntaxFactory all the time.

Or you can goto "http://roslynquoter.azurewebsites.net/" and paste your code in the small little textbox and click "Get Roslyn API calls to generate this code".
(I can generate the code you posted above, but it is kinda long. so i use a simple example.
For example, let's say you paste "DateTime mydate2 = new DateTime()", the tool will generate the following code :-
LocalDeclarationStatement(
VariableDeclaration(
IdentifierName("DateTime"))
.WithVariables(
SingletonSeparatedList<VariableDeclaratorSyntax>(
VariableDeclarator(
Identifier("mydate2"))
.WithInitializer(
EqualsValueClause(
ObjectCreationExpression(
IdentifierName("DateTime"))
.WithArgumentList(
ArgumentList())))))).WithSemicolonToken(
MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace()
Then you just have to fix up the code using SyntaxFactory, for example :-
var myDeclaratyion = SyntaxFactory.LocalDeclarationStatement(
SyntaxFactory.VariableDeclaration(
SyntaxFactory.IdentifierName("DateTime")).
WithVariables(
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
SyntaxFactory.VariableDeclarator(
SyntaxFactory.Identifier("mydate2")).
WithInitializer(
SyntaxFactory.EqualsValueClause(
SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.IdentifierName("DateTime"))
.WithArgumentList(
SyntaxFactory.ArgumentList())))))).WithSemicolonToken(SyntaxFactory.MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace();

Related

C# Create Deedle DataFrame from JSON response

I'm having a bit of trouble loading a JSON response from this request into a Deedle DataFrame:https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0/query?where=OBJECTID%3C10&returnGeometry=false&f=json
In the JSON, what I'm interested are the features. More specifically, for each feature there are attributes - I want essentially just a collection of those attributes to load into a DataFrame. In this particular case, there is only one attribute "name" so my expectation is that the resulting DataFrame would have a column "name" with the values shown.
I've tried using json2csharp and creating my own class, but the result either doesn't have the column header/values or the values are missing. I'm not really sure what I'm doing wrong or if I'm even approaching this the right way. My understanding from the Deedle documentation is that it should be possible to create a DataFrame from a collection of objects: https://bluemountaincapital.github.io/Deedle/csharpframe.html#Creating-and-loading-data-frames. Certainly, using the Enumerable example listed on the page works as expected.
Here is the pertinent section of my code:
string url = "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Earthquakes_Since1970/FeatureServer/0/query?";
WebClient wc = new WebClient();
wc.QueryString.Add("where", "OBJECTID<10");
wc.QueryString.Add("returnGeometry", "false");
wc.QueryString.Add("f", "json");
var data = wc.UploadValues(url, "POST", wc.QueryString);
var responseString = UnicodeEncoding.UTF8.GetString(data);
JObject o = JObject.Parse(responseString);
dynamic x = JsonConvert.DeserializeObject(responseString);
var testObjList = new List<dynamic>();
foreach (dynamic element in x.features)
{
testObjList.Add(new myClass { name = element.attributes.name});
Console.WriteLine($"{element.attributes.name}");
}
var dfObjects = Frame.FromRecords(testObjList);
dfObjects.Print();
var df = Frame.FromRecords(test);
df.Print(); // No headers or values shown
where myClass is just this:
public class myClass{
public string name { get; set; }
}
Any help/pointers would be much appreciated!
The Frame.FromRecords operation relies on static type information to figure out what properties the class has. In your case, you define the list of objects as List<dynamic> - this is compiled as Object and so Deedle does not see any members.
To fix this, all you need to do is to define the type as a list of myClass objects:
var testObjList = new List<myClass>();
A more compact approach using an anonymous type would work too:
var testObjList =
((IEnumerable<dynamic>)x.features).Select(element =>
new { name = element.attributes.name });
var dfObjects = Frame.FromRecords(testObjList);

CSharpScript: Dynamic script parameter names

I'm trying to use Roslyn to execute C# code that is defined by the user at runtime, similar to this example:
public class Globals
{
public int X;
public int Y;
}
var globals = new Globals { X = 1, Y = 2 };
Console.WriteLine(await CSharpScript.EvaluateAsync<int>("X+Y", globals: globals));
Example copied from here
My problem is that the variable names used in the script are unknown at compile time. In other words, I don't know what member-names I should use for my globals class and how many members (script-parameters) there will be.
I tried to use ExpandoObject to solve the problem but couldn't get it to work. Is ExpandoObject supposed to work in this context? Are there other ways to solve the problem?
Update
For my use case, the best solution is probably to use System.Linq.Dynamic:
//Expression typed in by the user at runtime
string Exp = #"A + B > C";
//The number of variables and their names are given elsewhere,
//so the parameter-array doesn't need to be hardcoded like in this example.
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[]
{
Expression.Parameter(typeof(double), "A"),
Expression.Parameter(typeof(double), "B"),
Expression.Parameter(typeof(double), "C")
},
null, Exp);
var Lambda = e.Compile();
//Fake some updates...
foreach (var i in Enumerable.Range(0,10))
{
Console.WriteLine(Lambda.DynamicInvoke(i, 3, 10));
}
If you can retrieve at runtime all member names, their count and their values that were passed from input you can generate execution code at runtime and evaluate it. As a simple example of execution code you can generate variable declarations for all input values and then sum all of them:
// here you should put retrieved member names and their values. Just for example, currently here exist a couple of args
var variablesAndValues = new Dictionary<string, object> { ["arg_1"] = 5, ["arg_2"] = 6, ["arg_3"] = 7 };
// and you should create an execution code looks like this:
// var arg_1 = value_1;
// ..
// var arg_n = value_n;
// arg_1 + .. + arg_n
StringBuilder builder = new StringBuilder();
foreach (var item in variablesAndValues)
{
builder.Append("var ").Append(item.Key).Append(" = ").Append(item.Value).AppendLine(";");
}
var variablesCount = variablesAndValues.Count;
foreach (var item in variablesAndValues.Keys)
{
builder.Append(item);
if (--variablesCount > 0)
{
builder.Append(" + ");
}
}
var scriptState = CSharpScript.RunAsync(builder.ToString()).Result;
var result = scriptState.ReturnValue;
Be careful, this example assumes that the all value types has sum_operation and them are known by default script options, else you will receive compile error when try to execute the code.
Upd.
If your cases are performance critically you may create a script that will sum all input arguments and then run this script repeatedly when you need it.
public class Globals
{
public int[] Args;
}
...
// create script that sum all input arguments
var script = CSharpScript.Create(#"var result = 0;
foreach (var item in Args)
{
result += item;
}
return result; ", globalsType: typeof(Globals));
// run it at twice on the user values that were received before
// also you can reuse an array, but anyway
var res1 = script.RunAsync(new Globals { Args = new[] { 5, 6, 7 } }).Result.ReturnValue;
var res2 = script.RunAsync(new Globals { Args = new[] { 7, 8, 9, 10} }).Result.ReturnValue;
This approach ignore in the script code the input variable names from an user, and seems that it doesn't matter in your cases.

Accessing Anonymous Types in C# ArrayList

I have simple C# ArrayList:
ArrayList sapTestData;
sapTestData = new ArrayList();
and I add elements with:
sapTestData.Add(new { Value = loanId, Text = description });
Yet when I try to access sapTestData, with
string test = sapTestData[1].Value;
I get a 'object' does not contain a definition for 'Value'.... error...
What is problem?
You can simply use generics.
Have an object like this:
var Loan = new { Value = “Loan1”, Text = description };
Add to list like this:
var loanList = GetLoanList(Loan);
Access it like below:
if(loanList.Any(n => n.Value == "Loan1"))
{
Console.WriteLine("Loan 1 is there");
}
Have a method like below:
public List<T> GetLoanList<T>(T item)
{
List<T> newLoanList = new List<T>();
newLoanList.Add(item);
return newLoanList;
}
var is a great feature in C# but sometimes in code like these inference is desirable.

Modifying body of method with Roslyn

Is there some way to add a specific statement at the end of a method in a .cs file with Roslyn?
var code = new StreamReader(#"C:\Users\PersonalUser\Documents\Visual Studio 2015\Projects\SampleToAnalyze\SampleToAnalyze\ClassChild.cs").ReadToEnd();
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();
var compilation = CSharpCompilation.Create("MyCompilation", new[] { tree },
new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) });
var model = compilation.GetSemanticModel(tree);
MethodDeclarationSyntax myMethod= tree.GetRoot().DescendantNodes()
.OfType<MethodDeclarationSyntax>().Last();
StatementSyntax myStatement= SyntaxFactory.ParseStatement(#"Console.WriteLine();");
I want to insert "myStatement" at the end of method "myMethod".
You can use the AddBodyStatements method to create a new MethodDeclarationSyntax with the statement added and then use ReplaceNode to update the SyntaxTree:
var newRoot = root.ReplaceNode(myMethod, myMethod.AddBodyStatements(myStatement));
This will create code that's valid C#, but looks wrong, because it's badly indented. Probably the simplest way to fix that is to also call Formatter.Format:
newRoot = Formatter.Format(newRoot, new AdhocWorkspace());

Assigning properties to an object

Okay so I have a small section of code which creates a list of objects based on the data model. I don't want to have to create a class for this. It is used on n ASP.net MVC application for populating a user notifications list.
I know there are plenty of other ways to do this such as actually setting up a class for it(probably the easiest method), but I would like to know if there is a way to do what is displayed below.
List<object> notificationList = new List<object>();
object userNotification = new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" };
notificationList.Add(userNotification);
foreach(object notification in notificationList)
{
string value = notification.Text;
}
So I haven't populated the list much but for the purposes here you get the idea. After debug I notice that the Text and Url properties exist, however cannot code to get the values???
You need to use dynamic as variable type in the foreach:
foreach(dynamic notification in notificationList)
{
string value = notification.Text;
}
Edit Oops ... you do need "dynamic", either as the List's generic type, or in the foreach.
var notificationList = new List<dynamic>();
var userNotification = new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" };
notificationList.Add(userNotification);
foreach (var notification in notificationList)
{
string value = notification.Text;
}
End edit
Anonymous types should be declared using the var keyword:
var userNotification = new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" };
You could also use "dynamic" instead of "var", but that deprives you of compile-time checks, and it appears unnecessary in this case (because the type is fully defined at compile time, within the same method scope). A case where you would need to use "dynamic" is where you want to pass the anonymous-typed object as a parameter to another function, eg:
void Func1()
{
var userNotification = new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" };
Func2(userNotification);
}
void Func2(dynamic userNotification)
{
string value = notification.Text;
}
Well you could declare the list as an list of dynimac objects:
List<dynamic> notificationList = new List<object>();
var userNotification = new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" };
notificationList.Add(userNotification);
foreach(dynamic notification in notificationList)
{
string value = notification.Text;
}
or use var to let the compiler choose the type:
var notificationList = new []
{
new { Text = "Here is some text!", Url = "http://www.google.com/#q=notifications" }
}.ToList();
foreach(var notification in notificationList)
{
string value = notification.Text;
}

Categories