使用语法树替换 C 中所有方法的现有属性

以下代码段替换了名为 PreviousAttribute 的所有属性,名为 ReplacementAttribute,用于整个解决方案。该示例手动搜索语法树并替换所有受影响的节点。

    static async Task<bool> ModifySolution(string solutionPath)
    {
        using (var workspace = MSBuildWorkspace.Create())
        {
            // Selects a Solution File
            var solution = await workspace.OpenSolutionAsync(solutionPath);
            // Iterates through every project
            foreach (var project in solution.Projects)
            {
                // Iterates through every file
                foreach (var document in project.Documents)
                {
                    // Selects the syntax tree
                    var syntaxTree = await document.GetSyntaxTreeAsync();
                    var root = syntaxTree.GetRoot();
                    // Finds all Attribute Declarations in the Document
                    var existingAttributesList = root.DescendantNodes().OfType<AttributeListSyntax>()
                        // Where the Attribute is declared on a method
                        .Where(curr => curr.Parent is MethodDeclarationSyntax)
                        // And the attribute is named "PreviousAttribute"
                        .Where(curr => curr.Attributes.Any(currentAttribute => currentAttribute.Name.GetText().ToString() == "PreviousAttribute"))
                        .ToList();
                    if (existingAttributesList.Any())
                    {
                        // Generates a replacement for every attribute
                        var replacementAttribute = SyntaxFactory.AttributeList(
                            SyntaxFactory.SingletonSeparatedList(
                            SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("ReplacementAttribute"),
                                SyntaxFactory.AttributeArgumentList(
                                    SyntaxFactory.SeparatedList(new[]
                                    {
                                    SyntaxFactory.AttributeArgument(
                                        SyntaxFactory.LiteralExpression(
                                            SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(@"Sample"))
                                        )
                                    })))));
                        // Replaces all attributes at once.
                        // Note that you should not use root.ReplaceNode
                        // since it would only replace the first note
                        root = root.ReplaceNodes(existingAttributesList, (node, n2) => replacementAttribute);
                        // Exchanges the document in the solution by the newly generated document
                        solution = solution.WithDocumentSyntaxRoot(document.Id, root);
                    }
                }
            }
            // applies the changes to the solution
            var result = workspace.TryApplyChanges(solution);
            return result;
        }
    }

可以针对以下类测试上面的示例:

public class Program
{
    [PreviousAttribute()]
    static void Main(string[] args)
    {
    }
}

你不应该使用 Methode root.ReplaceNode 来替换多个节点。由于树是不可变的,因此你将处理不同的对象。在上面的示例中使用以下代码段不会产生预期的结果:

foreach(var node in existingAttributesList){
    root = root.ReplaceNode(node, replacementAttribute);
}

第一次调用 ReplaceNode 会创建一个新的根元素。然而,existingAttributesList 中的元素属于不同的根(前一个根元素),因此无法替换。这将导致第一个属性被替换,并且以下属性保持不变,因为所有连续调用都将在新树中不存在的节点上执行。