简单注入器:注册一个动态创建的委托

Simple injector: Registering a dynamically created delegate

目标:

我正在尝试动态注册委托以在简单注入器容器中创建对象。基本上,我不仅希望能够从 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: Unable to cast object of type 'XXX.ProductRepository' to type 'System.Func'1[XXX.IProductRepository]'.]
lambda_method(Closure ) +83
lambda_method(Closure ) +179
SimpleInjector.Scope.CreateAndCacheInstance(ScopedRegistration'2 registration) +74
SimpleInjector.Scope.GetInstance(ScopedRegistration'2 registration) +260 SimpleInjector.Scope.GetInstance(ScopedRegistration'2 registration, Scope scope) +207
SimpleInjector.Advanced.Internal.LazyScopedRegistration'2.GetInstance(Scope scope) +241 lambda_method(Closure ) +310
SimpleInjector.InstanceProducer.GetInstance() +117

[ActivationException: The registered delegate for type ProductController threw an exception. Unable to cast object of type 'XXX.ProductRepository' to type 'System.Func'1[XXX.IProductRepository]'.]
SimpleInjector.InstanceProducer.GetInstance() +222
SimpleInjector.Container.GetInstance(Type serviceType) +148
SimpleInjector.Integration.Web.Mvc.SimpleInjectorDependencyResolver.GetService(Type serviceType) +137
System.Web.Mvc.DefaultControllerActivator.Create(RequestContext requestContext, Type controllerType) +87

[InvalidOperationException: An error occurred when trying to create a controller of type 'YYY.ProductController'. Make sure that the controller has a parameterless public constructor.] 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& controller, IControllerFactory& factory) +326 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +157
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +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& completedSynchronously) +155

我了解异常和导致它的问题,但我正在努力寻找一种正确的方法来实现所描述的功能。

所以如果我总结一下我的问题:

有没有一种方法可以执行类似于以下内容的注册:

container.Register<Func<IRepository>>(() => container.GetInstance<IRepository>)

对于在 运行 时间内解析的动态类型,类似于

container.Register<Func<T>>(() => container.GetInstance<T>)

这里的问题是您使用了错误的 Register 重载。你这样做:

container.Register(typeToRegister, () => container.GetInstance(t));

由于 C# 重载解析的工作方式,选择了以下重载:

Register(Type serviceType, Func<object> instanceCreator)

然而,此重载期望所提供的 Func<object> 的返回值是 serviceType 类型。但你的情况并非如此,因为你想注册委托本身。有几种方法可以解决这个问题。

例如,您可以为这个重载提供 Func<object>,returns 实际委托如下:

Func<object> func = () => container.GetInstance(t);
container.Register(typeToRegister, () => func);

但是由于你注册的delegate本身是一个单例,所以最好按如下方式进行:

Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, () => func);

这样更好,因为这可以防止 Diagnostic Services from reporting Lifestyle Mismatches 对您 Func<T> 注册的消费者造成影响。

但是由于您只想注册一个实例(委托),您还可以使用 RegisterSingleton(Type, object) 重载,如下所示:

Func<object> func = () => container.GetInstance(t);
container.RegisterSingleton(typeToRegister, (object)func);

效果是一样的,但在我看来这更干净一些。

但是请注意,none 给定的解决方案确实有效。它们不起作用,因为我们正在构造一个 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 配置成为 'polluted' 且 Func<T> 从未使用过的注册。这样可以更轻松地浏览注册并查看您的真实配置。

虽然说了这么多,但我担心的是:

For example if I have a service with a lot of dependencies, I don't want them to be created on service object creation but only when those specific dependencies need to be used.

即使是速度适中的容器,也能够以对象图的大小几乎不会成为问题的速度构建对象图。另一方面,Simple Injector 非常快。使用 Func<T> 延迟部分对象图的构建以提高性能是无用的,只会污染您的代码库。使用 Func<T> 作为抽象在某种意义上是 Leaky Abstraction (a violation of the Dependency Inversion Principle),因为依赖关系现在告诉消费者有关实现的一些信息;创造起来很重。但这是一个实现细节,消费者不应该被它所困扰。它使代码更难阅读,消费者更难测试。

但是,如果您对构建对象图所花费的时间有困难,则您的应用程序可能存在问题。 Injection constructors should be simple and fast. Letting a constructor do more than just storing the incoming dependencies is violation of the SRP.

正因如此,Simple Injector 不会立即为您构建 Func<T> 个委托。