You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在设计/编译时验证方法传入类型?鸭子类型分析器实现遇阻

鸭子类型转换库与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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 16:15:01