使用 Roslyn 排序构造函数参数

Order constructor parameters with Roslyn

我正在尝试创建对构造函数参数进行排序的方法。它导航良好,甚至更新树,但序列化文本包含原始参数:

    static void Transform(string sourceCode)
    {
        var tree = CSharpSyntaxTree.ParseText(sourceCode);

        var root = (CompilationUnitSyntax)tree.GetRoot();

        var @namespace = (NamespaceDeclarationSyntax)
            root.ChildNodes().First(n => n.Kind() == SyntaxKind.NamespaceDeclaration);

        var @class = (ClassDeclarationSyntax)
            @namespace.ChildNodes().First(n => n.Kind() == SyntaxKind.ClassDeclaration);

        var constructor = (ConstructorDeclarationSyntax)
           @class.ChildNodes().First(n => n.Kind() == SyntaxKind.ConstructorDeclaration);

        var parameters = constructor.ParameterList
            .ChildNodes()
            .Cast<ParameterSyntax>()
            .OrderBy(node => ((IdentifierNameSyntax) node.Type).Identifier.ToString())
            .Select(node => SyntaxFactory.Parameter(
                SyntaxFactory.List<AttributeListSyntax>(),
                SyntaxFactory.TokenList(),
                SyntaxFactory.ParseTypeName(((IdentifierNameSyntax)node.Type).Identifier.Text),
                SyntaxFactory.Identifier(node.Identifier.Text),
                null));

        var updatedParameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters));

        ((SyntaxNode)constructor).ReplaceNode(constructor.ParameterList, updatedParameterList);

        Console.WriteLine(root.GetText().ToString());
    }

我这样称呼它:

        Transform(@"
namespace Test {
    class Test {
        Test(IParam2 param2, IParam1 param1) { }
    }
}");

并期望

namespace Test {
    class Test {
        Test(IParam1 param1, IParam2 param2) { }
    }
}

但输出的参数顺序仍然错误。有什么想法吗?

问题是 Roslyn 中的所有树都是不可变的。

令人惊讶的是,这一行实际上并没有做任何事情:

((SyntaxNode)constructor).ReplaceNode(constructor.ParameterList, updatedParameterList);

那是因为 ReplaceNode() returns 一个全新的语法树,并没有操纵旧的语法树。

这就是您要查找的内容:

static void TransformParameterOrder(string sourceCode)
{
    var tree = CSharpSyntaxTree.ParseText(sourceCode);

    var root = (CompilationUnitSyntax)tree.GetRoot();

    var @namespace = (NamespaceDeclarationSyntax)
        root.ChildNodes().First(n => n.Kind() == SyntaxKind.NamespaceDeclaration);

    var @class = (ClassDeclarationSyntax)
        @namespace.ChildNodes().First(n => n.Kind() == SyntaxKind.ClassDeclaration);

    var constructor = (ConstructorDeclarationSyntax)
       @class.ChildNodes().First(n => n.Kind() == SyntaxKind.ConstructorDeclaration);

    var child = constructor.ParameterList.ChildNodes().Count();

    var parameters = constructor.ParameterList
        .ChildNodes()
        .Cast<ParameterSyntax>()
        .OrderBy(node => ((IdentifierNameSyntax)node.Type).Identifier.ToString())
        .Select(node => SyntaxFactory.Parameter(
            SyntaxFactory.List<AttributeListSyntax>(),
            SyntaxFactory.TokenList(),
            SyntaxFactory.ParseTypeName(((IdentifierNameSyntax)node.Type).Identifier.Text),
            SyntaxFactory.Identifier(node.Identifier.Text),
            null))
        .Take(2);

    var updatedParameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(parameters));

    var newNode = ((SyntaxNode)constructor).ReplaceNode(constructor.ParameterList, updatedParameterList);
    //Alternatively you can assign root = root.ReplaceNode...
    var newRoot = root.ReplaceNode(constructor.ParameterList, updatedParameterList);

    Console.WriteLine(root.GetText().ToString());
    Console.WriteLine(newRoot.GetText().ToString());
}