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
相关产品推荐
相关产品推荐

