简单注入器:注册动态创建的代理

时间:2015-05-05 14:30:30

标签: c# dependency-injection simple-injector

目标:

我正在尝试动态注册委托以在简单的注入器容器中创建对象。基本上我希望不仅能够从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>)

1 个答案:

答案 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>代表。