Roslyn 2.x CodeFix 实现了一个缺失的接口,委托给一个成员,VS 2017

Posted

技术标签:

【中文标题】Roslyn 2.x CodeFix 实现了一个缺失的接口,委托给一个成员,VS 2017【英文标题】:Roslyn 2.x CodeFix that implements a missing interface, delegated to a member, VS 2017 【发布时间】:2017-05-20 23:12:56 【问题描述】:

背景

我正在寻求创建一个 Roslyn CodeFix,它将响应来自 Visual Studio 附带的内置分析器的诊断警告,这将识别未实现或部分实现的接口,允许我遍历缺少的接口成员,并生成将方法调用委托给实现该接口的类型的成员字段的自定义代码。

(Visual Studio 附带的 Roslyn 分析器和 CodeFixes 确实提供了此功能,但我需要自定义和扩展代码的生成,这是不可能的,因为 Microsoft 实现都标记为 internal。)

请注意:接口几乎总是位于外部的第三方程序集中,我无法访问源代码。

例如起点:

public class CustomDocumentDBClient : IDocumentClient


期望的结果将类似于以下内容(实际上,一旦基本原则工作,我将创建多个版本,添加额外的代码来包装方法调用):

public class CustomDocumentDBClient : IDocumentClient

    // Field to which calls are delegated, initialised by the constructor
    private readonly IDocumentClient _documentClientImplementation;

    // Constructor
    public CustomDocumentDBClient (IDocumentClient documentClientImplementation)
    
        _documentClientImplementation = documentClientImplementation;
    

    // Interface method that delegates to private field for invocation 
    public Task<ResourceResponse<Attachment>> CreateAttachmentAsync(string documentLink, object attachment, RequestOptions options = null)
    
        return _documentClientImplementation.CreateAttachmentAsync(documentLink, attachment, options);
    

    // Interface method that delegates to private field for invocation    
    public Task<ResourceResponse<Attachment>> CreateAttachmentAsync(string documentLink, Stream mediaStream, MediaOptions options = null, RequestOptions requestOptions = null)
    
        return _documentClientImplementation.CreateAttachmentAsync(documentLink, mediaStream, options, requestOptions);
    
    ...
    other methods
    ...

我已经尝试过什么

我花了一些时间阅读有关 Roslyn 的语法树和语义模型功能的教程。

我还检查了来自 GitHub 的 Roslyn 源代码 - 确实包含了我希望实现的确切功能;然而,代码在各种复杂的类中被大量交织在一起,并且被实现为internal 方法,这些方法不能被覆盖或扩展,或者实际上不能被提取到一个独立的项目中。

通过调查多个样本以及相关的 SO 问题How to see if a class has implemented the interface with Roslyn,我得出结论,我必须使用 Roslyn 语义模型来获取有关接口的信息,并且它是声明的成员。

一旦我获得接口成员,我就可以使用SyntaxFactory 构建所需的输出代码,并且我使用了“Roslyn Quoter”作为指导。

从默认模板创建一个响应正确诊断代码的 CodeFix 非常简单,并且可以正常工作。

问题

我遇到的问题是获取诊断位置标识的令牌,这似乎是SimpleBaseTypeSyntax 令牌,并且

    验证它实际上代表一个接口, 获取允许我枚举第三方接口的成员的符号定义。

语法可视化器表明接口声明节点的类型为SimpleBaseType

因此,我使用以下代码从语法树中获取令牌 SimpleBaseTypeSyntax-

    // Find the interface Syntax Token detected by the diagnostic.
    var interfaceBaseTypeSyntax =
        root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf()
            .OfType<SimpleBaseTypeSyntax>().First();

a) 这确实返回了一个包含语法树中相关节点信息的标记 - 但是,我看不到任何“InterfaceTypeSyntax”类型或 IsInterface 方法来验证它实际上是一个接口。

b) 我相信我应该能够使用semanticModel.GetSymbolInfo(interfaceBaseTypeSyntax),但是这总是返回 null - 请记住接口是在外部程序集中声明的。

我需要做些什么来通过 GetSymbolInfo 提供这些信息,还是我应该采取其他方法...?

非常感谢您的建议...

【问题讨论】:

【参考方案1】:

发帖后这么快就找到了,挺尴尬的,不过解决办法好像是参考Identifier,它是SimpleBaseTypeSyntax的后代。

var interfaceSymbolInfo =
semanticModel.GetSymbolInfo(interfaceBaseTypeSyntax.DescendantNodes().First());

并且,通过调用:

var interfaceTypeInfo = 
semanticModel.GetTypeInfo(interfaceBaseTypeSyntax.DescendantNodes().First());

然后我可以使用interfaceTypeInfo.Type.IsInterface 来验证我确实找到了一个接口类型,并且还可以访问interfaceTypeInfo.Type.GetMembers()

答案是通过语法可视化器盯着我的脸。

我暂时保持开放状态,以防其他人有更好的解决方案...谢谢!

【讨论】:

不错的一个!经常问它会迫使你以另一种方式思考它...... @JeremyThompson - 是的,这正是发生的事情。我不得不重新审视我已经采取的所有步骤,然后我查看了语法可视化器,发现有子节点,特别是标识符,这当然是查找符号或类型信息所必需的逻辑...【参考方案2】:

在这种情况下,您正在查看的语法节点指的是类型而不是符号。如果您传入的节点不是符号,GetSymbolInfo 将返回 null。您想使用semanticModel.GetTypeInfo(interfaceBaseTypeSyntax)

【讨论】:

谢谢,但实际上我使用的是 interfaceBaseTypeSyntax,而不是其中包含的 Identifier Token 作为后代。

以上是关于Roslyn 2.x CodeFix 实现了一个缺失的接口,委托给一个成员,VS 2017的主要内容,如果未能解决你的问题,请参考以下文章

Roslyn 的 SyntaxReceiver - 获取类实现接口

Roslyn - 如何用多个节点替换多个节点?

C# Roslyn 替换方法

Roslyn导致发布网站时报错:编译失败

理解 Roslyn 中的红绿树(Red-Green Trees)

Roslyn 学习笔记