目标:
我正在尝试动态注册委托以在简单的注入器容器中创建对象。基本上我希望不仅能够从DI容器中获取实际实例,而且还能够使用可用于创建这些实例的方法。这对于延迟对象创建很有用。例如,如果我有一个具有大量依赖关系的服务,我不希望在创建服务对象时创建它们,但只有在需要使用这些特定依赖项时才能创建它们。
问题:
当我使用container.Register<T>
方法时,我可以成功注册代表:
container.Register<Func<IRepository>>(() => container.GetInstance<IRepository>);
当我只想在运行时知道类型时,我想动态注册那种委托:
这是我的代码:
private static void RegisterFunctions(Container container)
{
var types = container.GetCurrentRegistrations()
.Where(r => r.ServiceType.IsInterface && r.ServiceType != typeof(Func<>))
.Select(r => r.ServiceType);
var typesList = types as IList<Type> ?? types.ToList();
foreach (var t in typesList)
{
var typeToRegister = typeof(Func<>).MakeGenericType(t);
//This needs to be replaced:
container.Register(typeToRegister, () => container.GetInstance(t));
}
}
问题出在container.Register(typeToRegister, () => container.GetInstance(t));
我认为它的行为与container.Register<T>
方法相同,但我错了。
当我为以下场景运行此代码时:
public interface IProductService
{
}
public class ProductService : IProductService
{
public ProductService(Func<IProductRepository> getRepositoryFunc)
{
this.ProductRepositoryFunc = getRepositoryFunc;
}
}
我收到System.InvalidCastException
:
[InvalidCastException:无法转换类型&#39; XXX.ProductRepository&#39;的对象输入 &#39; System.Func&#39; 1 [XXX.IProductRepository]&#39 ;.]
lambda_method(关闭)+83
lambda_method(关闭)+179
SimpleInjector.Scope.CreateAndCacheInstance(ScopedRegistration&#39; 2 注册)+74
SimpleInjector.Scope.GetInstance(ScopedRegistration&#39; 2注册) +260 SimpleInjector.Scope.GetInstance(ScopedRegistration&#39; 2注册,范围范围)+207
SimpleInjector.Advanced.Internal.LazyScopedRegistration&#39; 2.GetInstance(范围 范围)+241 lambda_method(Closure)+310
SimpleInjector.InstanceProducer.GetInstance()+ 117[ActivationException:类型的已注册委托 ProductController引发了异常。无法投射物体 类型&#39; XXX.ProductRepository&#39;输入 &#39; System.Func&#39; 1 [XXX.IProductRepository]&#39 ;.]
SimpleInjector.InstanceProducer.GetInstance()+222
SimpleInjector.Container.GetInstance(Type serviceType)+148
SimpleInjector.Integration.Web.Mvc.SimpleInjectorDependencyResolver.GetService(类型 serviceType)+137
System.Web.Mvc.DefaultControllerActivator.Create(的RequestContext requestContext,Type controllerType)+87[InvalidOperationException:尝试创建时出错 控制器类型 &#39; YYY.ProductController&#39 ;. 确保控制器具有无参数的公共构造函数。] System.Web.Mvc.DefaultControllerActivator.Create(的RequestContext requestContext,Type controllerType)+247
System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(的RequestContext requestContext,Type controllerType)+438
System.Web.Mvc.DefaultControllerFactory.CreateController(的RequestContext requestContext,String controllerName)+257
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext,IController&amp;控制器,IControllerFactory&amp;厂) +326 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext,AsyncCallback回调,对象状态)+157
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback回调,对象状态)+88
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext的 context,AsyncCallback cb,Object extraData)+50
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301 System.Web.HttpApplication.ExecuteStep(IExecutionStep step,Boolean&amp; completedSynchronously)+155
我理解异常和导致它的问题,但我正在努力找到实现所述功能的正确方法。
所以,如果我总结一下我的问题:
有没有办法执行类似于以下的注册:
container.Register<Func<IRepository>>(() => container.GetInstance<IRepository>)
表示在运行时解析的动态类型,如
container.Register<Func<T>>(() => container.GetInstance<T>)
答案 0 :(得分:3)
这里的问题是您使用了错误的Register
重载。你这样做:
container.Register(typeToRegister, () => container.GetInstance(t));
由于C#重载解析的工作方式,选择了以下重载:
Register(Type serviceType, Func<object> instanceCreator)
但是,此重载期望来自提供的Func<object>
的返回值是serviceType
类型。但在您的情况下情况并非如此,因为您想要注册委托本身。有几种方法可以解决这个问题。
例如,您可以使用Func<object>
提供此重载,该重载将返回实际的委托,如下所示:
Func<object> func = () => container.GetInstance(t);
container.Register(typeToRegister, () => func);
但是,由于你注册的代表是一个单身人士,所以最好这样做:
Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, () => func);
这样做会更好,因为这会阻止Diagnostic Services向您Func<T>
注册的消费者报告Lifestyle Mismatches。
但是,由于您只想注册一个实例(委托),您还可以使用RegisterSingleton(Type, object)
重载,如下所示:
Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, (object)func);
效果是一样的,但这是一个更清洁的IMO。
请注意,没有一个给定的解决方案确实有效。他们没有工作,因为我们正在构建Func<object>
,同时尝试注册Func<SomethingElse>
,而Func<object>
无法投放,这将导致要么注册失败,要么应用程序在解析或注入此类实例时失败。
所以真正的解决方案是构造精确的Func<T>
类型并注册它。这正是文档the register factory delegates部分示例中的示例:
container.ResolveUnregisteredType += (s, e) =>
{
var type = e.UnregisteredServiceType;
if (!type.IsGenericType ||
type.GetGenericTypeDefinition() != typeof(Func<>))
return;
Type serviceType = type.GetGenericArguments().First();
InstanceProducer producer = container.GetRegistration(serviceType, true);
Type funcType = typeof(Func<>).MakeGenericType(serviceType);
var factoryDelegate =
Expression.Lambda(funcType, producer.BuildExpression()).Compile();
e.Register(Expression.Constant(factoryDelegate));
};
此处未注册的类型解析用于在后台动态构建Func<T>
个委托。效果与您对Register
的手动调用所做的大致相同,但行为更具确定性,因为您的RegisterFunctions
必须在所有其他注册之前被调用,而此时间为此方法没关系。它还可以防止您的DI配置被污染&#39;使用从未使用的Func<T>
个注册。这样可以更轻松地浏览注册并查看实际配置的外观。
尽管如此,以下担心我:
例如,如果我有一个有很多依赖关系的服务,我就不会这样做 希望它们在服务对象创建时创建,但仅限于那些 需要使用特定的依赖关系。
即使是速度适中的容器,也能够以对象图的大小几乎不成问题的速度构建对象图。另一方面,简单的注射器非常快。使用Func<T>
延迟构建对象图的部分性能是没用的,只会污染您的代码库。使用Func<T>
作为抽象在某种意义上是Leaky Abstraction(违反Dependency Inversion Principle),因为依赖现在告诉消费者一些关于实现的事情;创造它很重要。但这是一个实现细节,消费者不应该被这样做。它使代码更难阅读,消费者更难测试。
但是,如果您在构造对象图时花费了很多时间,那么您的应用程序就会出现问题。 Injection constructors should be simple而且很快。让构造函数执行的不仅仅是存储传入的依赖项,这违反了SRP。
因为这样,Simple Injector不会为您开箱Func<T>
代表。