Roslyn 向现有类添加新方法 [英] Roslyn add new method to an existing class

查看:48
本文介绍了Roslyn 向现有类添加新方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究在 Visual Studio 扩展 (VSIX) 中使用 Roslyn 编译器,该扩展使用 VisualStudioWorkspace 更新现有代码.在过去的几天里阅读了这个,似乎有几种方法可以实现这一点......我只是不确定哪种方法对我来说是最好的.

好的,让我们假设用户在 Visual Studio 2015 中打开了他们的解决方案.他们单击我的扩展程序,然后(通过表单)他们告诉我他们想要将以下方法定义添加到接口:

GetSomeDataResponse GetSomeData(GetSomeDataRequest request);

他们还告诉我接口的名称,它是 ITheInterface.

界面中已经有一些代码:

命名空间 TheProjectName.Interfaces{使用系统;公共接口 ITheInterface{///<总结>///一个孤独的方法.///</总结>LonelyMethodResponse LonelyMethod(LonelyMethodRequest 请求);}}

好的,所以我可以使用以下内容加载接口文档:

Document myInterface = this.Workspace.CurrentSolution?.Projects?.FirstOrDefault(p=>p.Name.Equals("TheProjectName"))?.文件?.FirstOrDefault(d=>d.Name.Equals("ITheInterface.cs"));

那么,现在将我的新方法添加到这个现有接口的最佳方法是什么,最好也写在 XML 注释(三斜线注释)中?请记住,请求和响应类型(GetSomeDataRequest 和 GetSomeDataResponse)实际上可能还不存在.我对此很陌生,所以如果您能提供代码示例,那就太棒了.

更新

我决定(可能)最好的方法是简单地注入一些文本,而不是尝试以编程方式构建方法声明.

我尝试了以下方法,但最终出现了一个我不理解的异常:

SourceText sourceText = await myInterface.GetTextAsync();字符串文本 = sourceText.ToString();var sb = new StringBuilder();//我想要所有的文本,包括最后一个//方法,但没有关闭接口和命名空间的}"sb.Append(text.Substring(0, text.LastIndexOf("}", text.LastIndexOf("}") - 1)));//现在添加我的方法并关闭接口和命名空间.sb.AppendLine("GetSomeDataResponse GetSomeData(GetSomeDataRequest request);");sb.AppendLine("}");sb.AppendLine("}");

检查一下,一切都很好(我的真实代码添加了格式和 XML 注释,但为了清晰起见删除了这些).

所以,知道这些是不可变的,我尝试如下保存:

var updatedSourceText = SourceText.From(sb.ToString());var newInterfaceDocument = myInterface.WithText(updatedSourceText);var newProject = newInterfaceDocument.Project;var newSolution = newProject.Solution;this.Workspace.TryApplyChanges(newSolution);

但这产生了以下异常:

bufferAdapter 不是 VsTextDocData

<块引用>

在 Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetAdapter(IVsTextBuffer bufferAdapter)在 Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer(IVsTextBuffer bufferAdapter)在 Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor(IServiceProvider serviceProvider, String filePath, Boolean needsSave, Boolean needsUndoDisabled)在 Microsoft.VisualStudio.LanguageServices.RoslynVisualStudioWorkspace.OpenInvisibleEditor(IVisualStudioHostDocument hostDocument)在 Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.DocumentProvider.StandardTextDocument.UpdateText(SourceText newText)在 Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.ApplyDocumentTextChanged(DocumentId documentId, SourceText newText)在 Microsoft.CodeAnalysis.Workspace.ApplyProjectChanges(ProjectChanges projectChanges)在 Microsoft.CodeAnalysis.Workspace.TryApplyChanges(Solution newSolution)在 Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges(Solution newSolution)

解决方案

如果我是你,我会利用 Roslyn 的所有好处,即我会使用 Document 的 SyntaxTree 而不是处理文件文本(您可以在不使用 Roslyn 的情况下完成后者).

例如:

<代码>...SyntaxNode root = await document.GetSyntaxRootAsync().ConfigureAwait(false);var interfaceDeclaration = root.DescendantNodes(node => node.IsKind(SyntaxKind.InterfaceDeclaration)).FirstOrDefault() as InterfaceDeclarationSyntax;if (interfaceDeclaration == null) 返回;var methodToInsert= GetMethodDeclarationSyntax(returnTypeName: "GetSomeDataResponse ",方法名称:GetSomeData",参数类型:新[] {GetSomeDataRequest"},参数名称:新[] {请求"});var newInterfaceDeclaration = interfaceDeclaration.AddMembers(methodToInsert);var newRoot = root.ReplaceNode(interfaceDeclaration, newInterfaceDeclaration);//这将格式化所有具有 Formatter.Annotation 的节点newRoot = Formatter.Format(newRoot, Formatter.Annotation, 工作区);工作区.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution);...public MethodDeclarationSyntax GetMethodDeclarationSyntax(string returnTypeName, string methodName, string[] parameterTypes, string[] paramterNames){var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(GetParametersList(parameterTypes, paramterNames)));return SyntaxFactory.MethodDeclaration(attributeLists: SyntaxFactory.List(),修饰符:SyntaxFactory.TokenList(),returnType: SyntaxFactory.ParseTypeName(returnTypeName),显式接口说明符:空,标识符:SyntaxFactory.Identifier(methodName),类型参数列表:空,参数列表:参数列表,约束条款:SyntaxFactory.List(),身体:空,分号:SyntaxFactory.Token(SyntaxKind.SemicolonToken))//注释这个节点应该被格式化.WithAdditionalAnnotations(Formatter.Annotation);}私有 IEnumerableGetParametersList(string[] parameterTypes, string[] paramterNames){for (int i = 0; i < parameterTypes.Length; i++){yield return SyntaxFactory.Parameter(attributeLists: SyntaxFactory.List(),修饰符:SyntaxFactory.TokenList(),类型:SyntaxFactory.ParseTypeName(parameterTypes[i]),标识符:SyntaxFactory.Identifier(paramterNames[i]),@默认:空);}}

请注意,这是非常原始的代码,Roslyn API 在分析/处理语法树、获取符号信息/引用等方面非常强大.我建议你看看这个 page 和这个 页面 供参考.

I'm investigating the use of the Roslyn compiler within a Visual Studio Extension (VSIX) that uses the VisualStudioWorkspace to update existing code. Having spent the last few days reading up on this, there seem to be several ways to achieve this....I'm just not sure which is the best approach for me.

Okay, so let's assume that the User has their solution open in Visual Studio 2015. They click on my Extension and (via a form) they tell me that they want to add the following method definition to an interface:

GetSomeDataResponse GetSomeData(GetSomeDataRequest request);

They also tell me the name of the interface, it's ITheInterface.

The interface already has some code in it:

namespace TheProjectName.Interfaces
{
    using System;
    public interface ITheInterface
    {
        /// <summary>
        ///    A lonely method.
        /// </summary>
        LonelyMethodResponse LonelyMethod(LonelyMethodRequest request);
    }
}

Okay, so I can load the Interface Document using the following:

Document myInterface = this.Workspace.CurrentSolution?.Projects?
    .FirstOrDefault(p 
        => p.Name.Equals("TheProjectName"))
    ?.Documents?
        .FirstOrDefault(d 
            => d.Name.Equals("ITheInterface.cs"));

So, what is the best way to now add my new method to this existing interface, ideally writing in the XML comment (triple-slash comment) too? Bear in mind that the request and response types (GetSomeDataRequest and GetSomeDataResponse) may not actually exist yet. I'm very new to this, so if you can provide code examples then that would be terrific.

UPDATE

I decided that (probably) the best approach would be simply to inject in some text, rather than try to programmatically build up the method declaration.

I tried the following, but ended up with an exception that I don't comprehend:

SourceText sourceText = await myInterface.GetTextAsync();
string text = sourceText.ToString();
var sb = new StringBuilder();

// I want to all the text up to and including the last
// method, but without the closing "}" for the interface and the namespace
sb.Append(text.Substring(0, text.LastIndexOf("}", text.LastIndexOf("}") - 1)));

// Now add my method and close the interface and namespace.
sb.AppendLine("GetSomeDataResponse GetSomeData(GetSomeDataRequest request);");
sb.AppendLine("}");
sb.AppendLine("}");

Inspecting this, it's all good (my real code adds formatting and XML comments, but removed that for clarity).

So, knowing that these are immutable, I tried to save it as follows:

var updatedSourceText = SourceText.From(sb.ToString());
var newInterfaceDocument = myInterface.WithText(updatedSourceText);
var newProject = newInterfaceDocument.Project;
var newSolution = newProject.Solution;
this.Workspace.TryApplyChanges(newSolution);

But this created the following exception:

bufferAdapter is not a VsTextDocData 

at Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetAdapter(IVsTextBuffer bufferAdapter) at Microsoft.VisualStudio.Editor.Implementation.VsEditorAdaptersFactoryService.GetDocumentBuffer(IVsTextBuffer bufferAdapter) at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.InvisibleEditor..ctor(IServiceProvider serviceProvider, String filePath, Boolean needsSave, Boolean needsUndoDisabled) at Microsoft.VisualStudio.LanguageServices.RoslynVisualStudioWorkspace.OpenInvisibleEditor(IVisualStudioHostDocument hostDocument) at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.DocumentProvider.StandardTextDocument.UpdateText(SourceText newText) at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.ApplyDocumentTextChanged(DocumentId documentId, SourceText newText) at Microsoft.CodeAnalysis.Workspace.ApplyProjectChanges(ProjectChanges projectChanges) at Microsoft.CodeAnalysis.Workspace.TryApplyChanges(Solution newSolution) at Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.VisualStudioWorkspaceImpl.TryApplyChanges(Solution newSolution)

解决方案

If I were you I would take advantage of all Roslyn benefits, i.e. I would work with the SyntaxTree of the Document rather than processing the files text (you are able to do the latter without using Roslyn at all).

For instance:

...
SyntaxNode root = await document.GetSyntaxRootAsync().ConfigureAwait(false);
var interfaceDeclaration = root.DescendantNodes(node => node.IsKind(SyntaxKind.InterfaceDeclaration)).FirstOrDefault() as InterfaceDeclarationSyntax;
if (interfaceDeclaration == null) return;

var methodToInsert= GetMethodDeclarationSyntax(returnTypeName: "GetSomeDataResponse ", 
          methodName: "GetSomeData", 
          parameterTypes: new[] { "GetSomeDataRequest" }, 
          paramterNames: new[] { "request" });
var newInterfaceDeclaration = interfaceDeclaration.AddMembers(methodToInsert);

var newRoot = root.ReplaceNode(interfaceDeclaration, newInterfaceDeclaration);

// this will format all nodes that have Formatter.Annotation
newRoot = Formatter.Format(newRoot, Formatter.Annotation, workspace);
workspace.TryApplyChanges(document.WithSyntaxRoot(newRoot).Project.Solution);
...

public MethodDeclarationSyntax GetMethodDeclarationSyntax(string returnTypeName, string methodName, string[] parameterTypes, string[] paramterNames)
{
    var parameterList = SyntaxFactory.ParameterList(SyntaxFactory.SeparatedList(GetParametersList(parameterTypes, paramterNames)));
    return SyntaxFactory.MethodDeclaration(attributeLists: SyntaxFactory.List<AttributeListSyntax>(), 
                  modifiers: SyntaxFactory.TokenList(), 
                  returnType: SyntaxFactory.ParseTypeName(returnTypeName), 
                  explicitInterfaceSpecifier: null, 
                  identifier: SyntaxFactory.Identifier(methodName), 
                  typeParameterList: null, 
                  parameterList: parameterList, 
                  constraintClauses: SyntaxFactory.List<TypeParameterConstraintClauseSyntax>(), 
                  body: null, 
                  semicolonToken: SyntaxFactory.Token(SyntaxKind.SemicolonToken))
          // Annotate that this node should be formatted
          .WithAdditionalAnnotations(Formatter.Annotation);
}

private IEnumerable<ParameterSyntax> GetParametersList(string[] parameterTypes, string[] paramterNames)
{
    for (int i = 0; i < parameterTypes.Length; i++)
    {
        yield return SyntaxFactory.Parameter(attributeLists: SyntaxFactory.List<AttributeListSyntax>(),
                                                 modifiers: SyntaxFactory.TokenList(),
                                                 type: SyntaxFactory.ParseTypeName(parameterTypes[i]),
                                                 identifier: SyntaxFactory.Identifier(paramterNames[i]),
                                                 @default: null);
    }
}

Note that this is pretty raw code, Roslyn API is extremely powerful when it comes to analyzing/processing the syntax tree, getting symbol information/references and so on. I would recommend you to look at this page and this page for reference.

这篇关于Roslyn 向现有类添加新方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆