如何使用 Roslyn 生成 class 字段的初始化

How to generate initialization of class fields with Roslyn

我知道如何在方法中创建局部变量,例如:

LocalDeclarationStatement(VariableDeclaration(IdentifierName("MyClass"))
            .WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier("nameOfvariable"))
                .WithInitializer(
                    EqualsValueClause(
                        ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
                            .WithNewKeyword(Token(SyntaxKind.NewKeyword)))))));

会给我:

MyClass nameOfvariable = new MyClass();

但是假设我已经创建了一个字段,现在我只想像这样初始化它(在方法、构造函数或任何东西中):

nameOfVariable = new MyClass();

我该怎么做?我猜它与 VariableDeclerator 有关,但我找不到正确的方法,所以我可以将它添加到包含 StatementSyntaxes 的列表中。我也可以将 VariableDecleration 更改为 "VariableDeclaration(IdentifierName(""))" 但这给了我一个丑陋的额外 space 语句。

我似乎在与 Roslyn 的一些非常基本的东西作斗争,我尝试检查 http://roslynquoter.azurewebsites.net/ 但这感觉像是被迫的方式(感觉它创建了比必要更多的代码)。

更新:应该说明我知道如何创建 method/constructors。当我只能访问字段名称和字段类型时,我只是在寻找一种初始化字段的方法。所以我想生成的唯一代码是:

myField = new MyField();

好吧,你就快完成了,你只需要创建所有这些。这应该可以满足您的兴趣:

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();

一些解释:您创建了一个新的标识符 MyClass,它将在您的 ObjectCreationExpression 中使用。然后将所有内容包装在 EqualsValueClause 中,并将其设置为声明符的初始值设定项。我们还向该节点添加了 Formatter 注释,以便稍后对其进行格式化,不会出现空白问题。

剩下的就是替换原始树中的节点,对其进行格式化,然后就完成了:

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

如果您的意思是要将赋值与声明分开,那么您必须创建一个新的 AssignmentExpression 并将其包装在 ExpressionStatement 中。通常表达式和语句是不同的概念,但是 ExpressionStatement 允许我们将表达式视为语句,这很重要,因为方法的主体只接受语句。

在代码中,它看起来像这样:

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();
}

结果:

经过更多的尝试和寻找,我找到了答案。您可以使用名为 "AssignmentExpression" 的东西。

这是一个如何使用它的例子:

ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, IdentifierName("myField"),
            ObjectCreationExpression(IdentifierName("MyClass")).WithArgumentList(arguments)
                .WithNewKeyword(Token(SyntaxKind.NewKeyword))));

这会给你:

myField = new Myclass(); 

所以现在很容易将创建和 assignment/initialization 分开为两个不同的语句。

请注意,我正在使用 "using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;",因此我不必一直编写 SyntaxFactory。

或者您可以转到“http://roslynquoter.azurewebsites.net/”并将您的代码粘贴到小文本框中,然后单击 "Get Roslyn API calls to generate this code"。

(我可以生成你在上面发布的代码,但它有点长。所以我使用一个简单的例子。

例如,假设您粘贴 "DateTime mydate2 = new DateTime()",该工具将生成以下代码:-

LocalDeclarationStatement(
VariableDeclaration(
    IdentifierName("DateTime"))
.WithVariables(
    SingletonSeparatedList<VariableDeclaratorSyntax>(
        VariableDeclarator(
            Identifier("mydate2"))
        .WithInitializer(
            EqualsValueClause(
                ObjectCreationExpression(
                    IdentifierName("DateTime"))
                .WithArgumentList(
                    ArgumentList())))))).WithSemicolonToken(
MissingToken(SyntaxKind.SemicolonToken)).NormalizeWhitespace()

然后您只需使用 SyntaxFactory 修复代码,例如 :-

   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();