C#反射调用泛型方法时如何在运行时动态生成匹配类型的Func委托
存在一个需要通过反射调用的外部泛型方法,签名如下:Foo<TResult>(Func<Bar, TResult> factory)
本次实现的核心难点是调用逻辑中的TResult类型无法在编译期确定,只能在运行时动态获取,需要支持全动态的类型处理。
最初尝试传入如下定义的委托作为反射调用Foo方法的参数:
Func<Bar, dynamic> factory = (bar) => { return tResultInstance; };
说明:上述实现确实不需要使用入参Bar,并非代码笔误。
实际反射调用Foo方法时会抛出类型转换异常,提示无法将System.Object类型转换为TResult类型。
核心诉求:确认是否可以在运行时动态创建类型完全匹配的Func委托,用于泛型方法的反射调用。
以动态调用依赖注入框架的AddScoped方法为例:
静态调用写法
var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped<Baz>(_ => new Baz("string"));
初始动态实现(报错版本)
private const string AddScopedMethodName = "AddScoped"; private const string AddScopedMethodSignature = "Microsoft.Extensions.DependencyInjection.IServiceCollection AddScoped[TService](Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Func`2[System.IServiceProvider,TService])"; <...> // bazType为运行时获取的Baz类型 var bazConstructorInfo = bazType.GetConstructor(new Type[1] { typeof(String) }); var bazConstructorParameters = new object[1] { inputString // inputString值为"string" }; var bazInstance = bazConstructorInfo.Invoke(bazConstructorParameters); var addScopedMethodInfo = typeof(ServiceCollectionServiceExtensions) .GetMethods() .Where(method => method.IsPublic && method.IsGenericMethod && (method.Name == AddScopedMethodName) && (method.ToString() == AddScopedMethodSignature)) .Single(); var addScopedMethod = addScopedMethodInfo.MakeGenericMethod(bazType); Func<IServiceProvider, dynamic> implementationFactory = (serviceProvider) => { return bazInstance; }; var addScopedMethodParameters = new object[2] { services, // services为IServiceCollection实例 implementationFactory }; addScopedMethod.Invoke( services, addScopedMethodParameters); // 抛出异常:Object of type 'System.Func`2[System.IServiceProvider,System.Object]' cannot be converted to type 'System.Func`2[System.IServiceProvider,Baz]'
运行上述代码会抛出类型转换异常,提示无法将System.Func2[System.IServiceProvider,System.Object]类型的对象转换为System.Func2[System.IServiceProvider,Baz]类型。
有参考方案提出可以通过反射获取预先定义的、返回固定类型的静态方法来构造对应委托,示例代码如下:
static Foo CreateFoo() { return new Foo(); } <...> var method = typeof(Program) .GetMethod( "CreateFoo", BindingFlags.NonPublic | BindingFlags.Static);
该方案不适用于当前场景:本场景中具体服务类型在运行前完全未知,无法预先编写返回固定类型的静态方法,无法解决问题。
可以通过Delegate.CreateDelegate方法在运行时动态构造类型完全匹配的Func委托,不需要预先定义固定类型的静态方法,完全适配全动态类型场景。
针对上述AddScoped的示例,修正后的核心代码如下:
// 前置逻辑:获取泛型方法、构造服务实例的代码保持不变 // 1. 构造目标委托的精确类型:Func<入参类型, 运行时获取的TResult类型> var targetFuncType = typeof(Func<,>).MakeGenericType(typeof(IServiceProvider), bazType); // 2. 写通用逻辑的委托模板,入参、返回值都用object即可 Func<IServiceProvider, object> factoryTemplate = sp => bazInstance; // 3. 基于模板委托创建类型完全匹配的目标委托实例 var matchedFactory = Delegate.CreateDelegate(targetFuncType, factoryTemplate.Target, factoryTemplate.Method); // 4. 传入类型匹配的委托,反射调用泛型方法即可正常执行 var addScopedMethodParameters = new object[2] { services, matchedFactory }; addScopedMethod.Invoke(services, addScopedMethodParameters);
如果需要在委托里加额外处理逻辑,直接在factoryTemplate里写即可,不需要提前知道具体的TResult类型。
报错原因说明:
dynamic本质是C#提供的语法糖,编译后Func<Bar, dynamic>会被统一编译为Func<Bar, object>,和泛型方法要求的Func<Bar, 具体TResult类型>是完全独立的委托类型,委托不支持这种跨泛型参数的隐式类型转换,所以直接传必然抛出类型转换异常,必须在运行时构造和目标泛型参数完全匹配的委托实例。
内容的提问来源于stack exchange,提问作者Ivan

