Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to generate initialization of class fields with Roslyn

Tags:

c#

roslyn

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();
like image 443
MilleB Avatar asked Dec 30 '15 15:12

MilleB


2 Answers

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:

enter image description here

-------------------------------------------------------------------------------

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:

enter image description here

like image 167
Jeroen Vannevel Avatar answered Nov 13 '22 06:11

Jeroen Vannevel


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.

like image 43
MilleB Avatar answered Nov 13 '22 07:11

MilleB