【问题标题】:C# Roslyn API: Insert Instructions/Methods between each nodes membersC# Roslyn API:在每个节点成员之间插入指令/方法
【发布时间】:2021-10-12 17:46:45
【问题描述】:

我刚进入一个个人项目,我希望能够使用 Roslyn API 在类成员之间插入指令/方法(所以方法/指令)

我目前能够像这里一样检索不同的子节点:命名空间>类>字段/方法

但我想知道如何在字段/方法之间插入代码而不是替换代码。

(以下图红线位置为例)

编辑:经过更多研究,我发现我可以使用InsertTokensBeforeInsertNodesBeforeInsertTriviaBefore,但我不明白如何将我的函数(实际上是文本/字符串格式)解析为所需的参数。

【问题讨论】:

    标签: c# .net roslyn insertion


    【解决方案1】:

    您可以使用Microsoft.CodeAnalysis.CSharp.SyntaxFactory 来构建Microsoft.CodeAnalysis.SyntaxNode

    要获得创建新语法树所需的语法工厂代码,请查看出色的 RoslynQuoter。 在那里,您可以通过语法工厂 API 放入您想要构建的 C# 程序:

    public class RoslynClass
    {
        public static int RoslynMethod(int left, int right)
        {
            return left + right;
        }
    }
    

    根据您的喜好检查所有选项,并获取 Roslyn API 调用以生成此代码!,您可以(部分)在生产代码中使用。

    这里我列出了一个源代码重构的示例,它在字段方法之前/之后插入方法public static int RoslynMethod(int left, int right) >:

    using System.Composition;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.CodeAnalysis;
    using Microsoft.CodeAnalysis.CodeActions;
    using Microsoft.CodeAnalysis.CodeRefactorings;
    using Microsoft.CodeAnalysis.CSharp;
    using Microsoft.CodeAnalysis.CSharp.Syntax;
    using Microsoft.CodeAnalysis.Editing;
    
    namespace RoslynTool
    {
        [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(MethodInserter))]
        [Shared]
        internal sealed class MethodInserter : CodeRefactoringProvider
        {
            public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
            {
                SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
                SyntaxNode node = root.FindNode(context.Span);
    
                if (node is FieldDeclarationSyntax or MethodDeclarationSyntax)
                {
                    SyntaxNode newNode = CreateMethodNode();
    
                    context.RegisterRefactoring(CodeAction.Create("Insert method before", ct => InsertBeforeAsync(context.Document, node, newNode, ct)));
                    context.RegisterRefactoring(CodeAction.Create("Insert method after", ct => InsertAfterAsync(context.Document, node, newNode, ct)));
                }
            }
    
            private static MemberDeclarationSyntax CreateMethodNode()
            {
                return SyntaxFactory.MethodDeclaration(
                    SyntaxFactory.PredefinedType(
                        SyntaxFactory.Token(SyntaxKind.IntKeyword)),
                    SyntaxFactory.Identifier("RoslynMethod"))
                    .WithModifiers(
                        SyntaxFactory.TokenList(new[] {
                            SyntaxFactory.Token(SyntaxKind.PublicKeyword),
                            SyntaxFactory.Token(SyntaxKind.StaticKeyword)}))
                    .WithParameterList(
                        SyntaxFactory.ParameterList(
                            SyntaxFactory.SeparatedList<ParameterSyntax>(new SyntaxNodeOrToken[] {
                                SyntaxFactory.Parameter(
                                    SyntaxFactory.Identifier("left"))
                                .WithType(
                                    SyntaxFactory.PredefinedType(
                                        SyntaxFactory.Token(SyntaxKind.IntKeyword))),
                                SyntaxFactory.Token(SyntaxKind.CommaToken),
                                SyntaxFactory.Parameter(
                                    SyntaxFactory.Identifier("right"))
                                .WithType(
                                    SyntaxFactory.PredefinedType(
                                        SyntaxFactory.Token(SyntaxKind.IntKeyword)))})))
                    .WithBody(
                        SyntaxFactory.Block(
                            SyntaxFactory.SingletonList<StatementSyntax>(
                                SyntaxFactory.ReturnStatement(
                                    SyntaxFactory.BinaryExpression(
                                        SyntaxKind.AddExpression,
                                        SyntaxFactory.IdentifierName("left"),
                                        SyntaxFactory.IdentifierName("right"))))));
            }
    
            private static async Task<Document> InsertBeforeAsync(Document document, SyntaxNode location, SyntaxNode newNode, CancellationToken cancellationToken)
            {
                DocumentEditor documentEditor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
                documentEditor.InsertBefore(location, newNode);
    
                return documentEditor.GetChangedDocument();
            }
    
            private static async Task<Document> InsertAfterAsync(Document document, SyntaxNode location, SyntaxNode newNode, CancellationToken cancellationToken)
            {
                DocumentEditor documentEditor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
                documentEditor.InsertAfter(location, newNode);
    
                return documentEditor.GetChangedDocument();
            }
        }
    }
    

    另外,SyntaxFactoryparse 方法可用于创建新的 SyntaxNode,但在该示例中我很难正确获取空格/琐事:

    namespace RoslynTool
    {
        internal sealed class MethodInserter : CodeRefactoringProvider
        {
            private static MemberDeclarationSyntax CreateMethodNode()
            {
                return SyntaxFactory.ParseMemberDeclaration(@"
            public static int RoslynMethod(int left, int right)
            {
                return left + right;
            }
    ");
            }
        }
    }
    

    希望我正确理解了您的问题。

    【讨论】:

    • 我不确定这是否符合我的要求,因为所有这些都需要在运行时完成,我想使用 RoselynAPI 将要在运行时构建的资源源变形为。此外,由于事先不知道要插入的方法,我无法将其转换为 C# RoslynMethod。我需要将它们作为“文本”或从文本中解析。无论如何,感谢您的帮助
    • @0x0000000000000 您要从中生成 C# 代码的资源仅在运行时可用,还是在编译时已完全可访问?因为如果资源在编译时已知,并且您使用的是 .NET 5.0 SDK(或更高版本),那么 Source Generators 可能会帮助您。这里需要注意的是,您不能将任何新代码插入现有文件,而是将您的类型标记为partial,然后在dotnet build 期间通过源生成器“扩展”它。
    • 流程其实是:从资源嵌入文件(.CS)中获取Source,运行时修改(We are here),然后在运行时使用CSharpCode Provider编译代码。资源以字符串形式获取,它是一个常见的 CS 源。除了在运行时使用 Roselyn 进行修改外,一切都已完成。对于插入的代码,我们事先并不知道,但我们也将其作为字符串获取。
    • 因为实际上没有其他人提出更接近我的问题的解决方案,如果没有人很快就会奖励你。
    • @0x0000000000000 恐怕我对运行时代码生成不是很熟悉。我知道在设计时使用 代码修复/重构 生成代码,以及通过 source generators 生成编译时代码,但我对代码生成的 XP 很少在运行时。对不起。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-20
    • 2018-03-24
    • 2021-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多