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

C# 泛型扩展方法如何仅指定转换器类型一个泛型参数即可调用

实现原理

C# 泛型参数不支持部分指定,要么全部由编译器推断,要么全部显式传入,因此原有的双泛型参数方法无法直接只传转换器类型。我们可以新增仅带转换器泛型参数的扩展方法,通过反射从转换器实现的接口中提取返回值类型,完成调用逻辑。


方案1:简单反射实现(适合低频调用场景)

public static class DocumentExtensions
{
    // 保留原有双泛型参数方法
    public static async Task<TResponse> Convert<TConverter, TResponse>(this IDocument document)
        where TConverter : IHtmlConverter<TResponse>, new()
        where TResponse : class
    {
        var htmlConverter = new TConverter();
        return await htmlConverter.Convert(document);
    }

    // 新增单泛型参数重载
    public static async Task<dynamic> Convert<TConverter>(this IDocument document)
        where TConverter : new()
    {
        var converter = new TConverter();
        // 校验转换器是否实现了IHtmlConverter<>接口
        var converterInterface = typeof(TConverter).GetInterfaces()
            .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHtmlConverter<>));
        if (converterInterface == null)
        {
            throw new InvalidOperationException($"{typeof(TConverter).Name} 未实现IHtmlConverter<TResponse>接口");
        }
        // 反射调用Convert方法
        var convertMethod = converterInterface.GetMethod(nameof(IHtmlConverter<object>.Convert));
        var resultTask = (Task)convertMethod.Invoke(converter, new object[] { document });
        await resultTask;
        // 读取异步返回结果
        var resultProperty = resultTask.GetType().GetProperty(nameof(Task<object>.Result));
        return resultProperty.GetValue(resultTask);
    }
}

调用方式和预期完全一致:

ItemsResponse response = await document.Convert<ItemsConverter>();

方案2:泛型缓存实现(适合高频调用场景)

该方案通过表达式树预编译调用逻辑,每个转换器类型仅反射一次,后续调用直接走缓存委托,性能和原有双泛型方法几乎无差异:

public static class DocumentExtensions
{
    // 内部泛型缓存类,每个TConverter仅初始化一次
    private static class ConverterExecutor<TConverter>
        where TConverter : new()
    {
        private static readonly Func<TConverter, IDocument, Task<object>> _executeDelegate;

        static ConverterExecutor()
        {
            var interfaceType = typeof(TConverter).GetInterfaces()
                .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHtmlConverter<>));
            var responseType = interfaceType.GetGenericArguments()[0];
            // 构造调用表达式并编译为委托缓存
            var converterParam = Expression.Parameter(typeof(TConverter), "converter");
            var documentParam = Expression.Parameter(typeof(IDocument), "document");
            var convertCall = Expression.Call(
                Expression.Convert(converterParam, interfaceType),
                nameof(IHtmlConverter<object>.Convert),
                Type.EmptyTypes,
                documentParam);
            var resultProperty = convertCall.Type.GetProperty(nameof(Task<object>.Result));
            var resultAccess = Expression.Convert(Expression.MakeMemberAccess(convertCall, resultProperty), typeof(object));
            var taskContinueMethod = typeof(Task<>).MakeGenericType(responseType)
                .GetMethod(nameof(Task<object>.ContinueWith), new[] { typeof(Func<,>).MakeGenericType(convertCall.Type, typeof(object)) });
            var continueWithCall = Expression.Call(
                convertCall,
                taskContinueMethod,
                Expression.Lambda(resultAccess, Expression.Parameter(convertCall.Type)));
            _executeDelegate = Expression.Lambda<Func<TConverter, IDocument, Task<object>>>(
                continueWithCall, converterParam, documentParam).Compile();
        }

        public static async Task<object> Execute(IDocument document)
        {
            var converter = new TConverter();
            return await _executeDelegate(converter, document);
        }
    }

    // 原有双泛型参数方法
    public static async Task<TResponse> Convert<TConverter, TResponse>(this IDocument document)
        where TConverter : IHtmlConverter<TResponse>, new()
        where TResponse : class
    {
        return await new TConverter().Convert(document);
    }

    // 单泛型参数重载
    public static async Task<dynamic> Convert<TConverter>(this IDocument document)
        where TConverter : new()
    {
        return await ConverterExecutor<TConverter>.Execute(document);
    }
}

内容的提问来源于stack exchange,提问作者Almis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 22:09:03