如何在设计/编译时验证方法传入类型?鸭子类型分析器实现遇阻
鸭子类型转换库与Roslyn分析器整合问题
场景背景
不同领域/项目/系统生成大量重叠类型,此前使用Automapper保障类型转换的安全性与正确性,现计划采用**鸭子类型(duck typing)**方案替代。
构思的实现环节
- 创建包含
Convert<TSource, TTarget>、ConvertSubset<TSource, TTarget>、ConvertSuperset<TSource, TTarget>方法的NuGet包,支持重写转换逻辑,通过自定义标记注解方便后续代码分析定位 - 编写Roslyn分析器,扫描上述方法的调用,提取泛型类型参数,递归验证TSource与TTarget的属性是否等价(可借助DeepEqual)
- 将鸭子类型转换库与Roslyn分析器打包为单个NuGet包,用户安装后自动启用分析器
当前卡点
已学习Microsoft Learn及Meziantou博客的Roslyn分析器教程,但仍不清楚如何针对特定方法调用添加检查。尝试过用自定义标记属性(如DuckTypeEqualAttribute)标记方法、重构为泛型类,均因C#特性限制报错。目前回到「标记方法+分析器定位调用」的方案,但无法继续推进。
需求
希望获得以下支持:
- 针对当前方案的具体实现指导
- 同类实现的代码示例
- 替代Roslyn分析器的可行方案建议
DuckTyping类库代码
using System; namespace DuckTyping { //marker attributes for roslyn analyzer to find methods class DuckTypeEqualAttribute: Attribute {} class DuckTypeSubsetAttribute: Attribute {} class DuckTypeSupersetAttribute: Attribute {} public class DuckTyping { /// <summary> /// static Converter function. Override with desired conversion function. Recommend NewtonsoftJson Convert. /// </summary> public static Func<object, Type, Type, object> Converter { get; set; } = (obj, t1, t2) => System.Convert.ChangeType(obj, t2);//avoid an explicit reference to newtonsoft json, user to override impl at startup [DuckTypeEqual] public static TDest Convert<TSource, TDest>(TSource obj) => (TDest) Converter(obj, typeof(TSource), typeof(TDest)); [DuckTypeSubset] public static TDest ConvertSubset<TSource, TDest>(TSource obj) => (TDest) Converter(obj, typeof(TSource), typeof(TDest)); [DuckTypeSuperset] public static TDest ConvertSuperset<TSource, TDest>(TSource obj) => (TDest) Converter(obj, typeof(TSource), typeof(TDest)); } }
Roslyn分析器代码
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Operations; namespace DuckTyping { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class DuckTypingAnalyzer : DiagnosticAnalyzer { public const string DiagnosticId = "DuckTyping"; private static readonly LocalizableString Title = new LocalizableResourceString(nameof(Resources.AnalyzerTitle), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(Resources.AnalyzerMessageFormat), Resources.ResourceManager, typeof(Resources)); private static readonly LocalizableString Description = new LocalizableResourceString(nameof(Resources.AnalyzerDescription), Resources.ResourceManager, typeof(Resources)); private const string Category = "Type Checking"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } } public override void Initialize(AnalysisContext context) { context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); context.RegisterSymbolAction(handler, SymbolKind.Method); context.RegisterOperationAction(handler, OperationKind.ExpressionStatement); } private static void handler(SymbolAnalysisContext ctx) { var methodSymbol = (IMethodSymbol)ctx.Symbol; if (false) // ??? somehow check if the method called is DuckTyping.Convert { // ??? somehow extract the generic types in the call //use DeepEqual to verify equal types, if not equal, report ctx.ReportDiagnostic(Diagnostic.Create(Rule, methodSymbol.Locations[0], methodSymbol.Name)); } } private static void handler(OperationAnalysisContext ctx) { var expressionStatement = (IExpressionStatementOperation)ctx.Operation; if (false) // ??? somehow check if the expression statement is calling DuckTyping.Convert { // ??? somehow extract the generic types in the call //use DeepEqual to verify equal types, if not equal, report ctx.ReportDiagnostic(Diagnostic.Create(Rule, expressionStatement.Syntax.GetLocation(), expressionStatement.Syntax.GetText())); } } } }
内容的提问来源于stack exchange,提问作者mjg
相关产品推荐
相关产品推荐

