在Microsoft DI工厂方法模式中,如何将委托注册后解析为Func?
问题场景与需求
我们有一系列结构相似的ViewModel类,每个类内部都定义了专属的工厂委托,构造函数同时包含业务传入的自定义Data参数和需从DI容器注入的服务:
public class ViewModel1 { public delegate ViewModel1 Factory(List<string> data); public ViewModel1(List<string> data, IOtherService service) { } } public class ViewModel2 { public delegate ViewModel2 Factory(int data); public ViewModel2(int data, IOtherService service) { } } // 更多同结构ViewModel类...
创建这类ViewModel时,我们希望由容器自动注入IOtherService,同时从业务逻辑灵活传入Data参数。常规做法是为每个ViewModel单独注册工厂委托:
// 注册ViewModel1的工厂 services.AddScoped<ViewModel1.Factory>(sp => { var factory = ActivatorUtilities.CreateFactory(typeof(ViewModel1), new[] { typeof(List<string>) }); return data => (ViewModel1)factory.Invoke(sp, new object[] { data }); }); // 使用示例 var factory = sp.GetRequiredService<ViewModel1.Factory>(); var vm = factory(new List<string> { "test", "data" });
但我们需要一套泛化的通用方案,能通过Func<TData, TModel>统一解析,实现如下简洁调用:
public static TModel GetVM<TModel, TData>(this ServiceProvider sp, TData data) { var factory = sp.GetRequiredService<Func<TData, TModel>>(); return factory(data); } // 目标调用方式 var vm = sp.GetVM<ViewModel2, int>(data: 42);
核心问题:.NET默认DI容器不支持Autofac那样的自动委托兼容解析(比如自动将Func<int, ViewModel2>映射到已注册的ViewModel2.Factory),如何实现这一特性?
解决方案
要让.NET DI支持Func<TIn, TRes>与自定义工厂委托的兼容解析,可通过以下几种方式实现:
1. 扩展GetVM方法:自动匹配并转换自定义工厂委托
直接修改泛型扩展方法,让它自动查找ViewModel内部的Factory委托,转换为Func<TData, TModel>后调用:
using System.Reflection; public static TModel GetVM<TModel, TData>(this IServiceProvider sp, TData data) { // 查找ViewModel内部的Factory委托类型 var factoryType = typeof(TModel).GetNestedTypes(BindingFlags.Public | BindingFlags.Instance) .First(t => t.Name == "Factory" && t.IsSubclassOf(typeof(MulticastDelegate))); // 从容器获取已注册的Factory委托 var factoryDelegate = sp.GetRequiredService(factoryType); // 将自定义委托转换为标准Func<TData, TModel> var func = (Func<TData, TModel>)Delegate.CreateDelegate( typeof(Func<TData, TModel>), factoryDelegate, factoryType.GetMethod("Invoke")); return func(data); }
调用方式保持不变,无需额外修改注册逻辑:
var vm = sp.GetVM<ViewModel2, int>(42);
2. 自定义ServiceProvider:全局支持委托兼容解析
如果希望全局范围内都能直接解析Func<TIn, TRes>,可以创建一个装饰性的ServiceProvider,自动处理委托类型匹配:
using System.Reflection; public class DelegateCompatibleServiceProvider : IServiceProvider { private readonly IServiceProvider _innerProvider; public DelegateCompatibleServiceProvider(IServiceProvider innerProvider) { _innerProvider = innerProvider; } public object GetService(Type serviceType) { // 仅处理Func<TIn, TRes>类型的解析请求 if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(Func<,>)) { var genericArgs = serviceType.GetGenericArguments(); var inputType = genericArgs[0]; var resultType = genericArgs[1]; // 查找结果类型内部的Factory委托 var factoryType = resultType.GetNestedTypes(BindingFlags.Public | BindingFlags.Instance) .FirstOrDefault(t => t.Name == "Factory" && t.IsSubclassOf(typeof(MulticastDelegate))); if (factoryType != null) { // 验证委托签名是否匹配Func<TIn, TRes> var invokeMethod = factoryType.GetMethod("Invoke"); var invokeParams = invokeMethod.GetParameters(); if (invokeParams.Length == 1 && invokeParams[0].ParameterType == inputType && invokeMethod.ReturnType == resultType) { // 获取已注册委托并转换为目标Func类型 var factoryDelegate = _innerProvider.GetRequiredService(factoryType); return Delegate.CreateDelegate(serviceType, factoryDelegate, invokeMethod); } } } // 非Func类型直接转发给原容器 return _innerProvider.GetService(serviceType); } }
使用时包装原ServiceProvider即可:
// 构建容器后包装 var sp = new DelegateCompatibleServiceProvider(serviceProvider); // 现在可直接解析Func<TIn, TRes> var factory = sp.GetRequiredService<Func<int, ViewModel2>>(); var vm = factory(42);
3. 简化方案:直接注册Func<TData, TModel>
如果不需要保留ViewModel内部的Factory委托定义,可跳过自定义委托,直接注册标准Func<TData, TModel>:
public static IServiceCollection AddViewModelFunc<TModel, TData>(this IServiceCollection services) { services.AddScoped<Func<TData, TModel>>(sp => { var factory = ActivatorUtilities.CreateFactory(typeof(TModel), new[] { typeof(TData) }); return data => (TModel)factory.Invoke(sp, new object[] { data }); }); return services; } // 注册示例 services.AddViewModelFunc<ViewModel1, List<string>>(); services.AddViewModelFunc<ViewModel2, int>(); // 使用时直接解析Func var factory = sp.GetRequiredService<Func<int, ViewModel2>>(); var vm = factory(42);
内容的提问来源于stack exchange,提问作者Serg
相关产品推荐
相关产品推荐

