c# - 简单注入(inject)器:如何跳过对容器中对象的验证

标签 c# dependency-injection simple-injector

我正在使用简单注入器通过构造函数注入将依赖项注入到我的对象中。

对于一组特定的对象(所有对象都从一个通用的抽象基类派生),我注入了一个工厂而不是具体对象,以便可以在运行时确定应注入哪个派生实例。工厂和单个实例在容器中注册为单例。

将所有对象注册到DI容器后,我调用Container.Verify()方法来验证我的配置。问题是,此方法的实现会创建向容器注册的每种类型的实例。这些派生的实例创建起来很昂贵,并且它们的创建有副作用(它们来自正在更新以使用DI的旧代码)。从长远来看,我会摆脱副作用,但从短期来看,这是不可行的。

如何告诉容器不要验证这些派生实例?

我想保留Verify()调用(至少对于调试版本而言),但是不能接受Verify()调用实例化的这些实例。此外,它们需要在Singleton Lifestyle中注册,所以我不能只在容器中注册它们。

我想出的一个解决方案是不将派生对象注册到容器中(从而使它们不可验证),然后让(Singleton Lifestyle)Factory实现缓存它创建的第一个实例。它可以工作,但是很脏,如果有人在其他地方(不太可能)显式请求派生类型的实例,则将创建一个新实例(因为默认的Lifestyle是Transient)。

这是我拥有的类结构的示例:

public class AbstractBase { }
public class Derived1 : AbstractBase { }
public class Derived2 : AbstractBase { }
public class Derived3 : AbstractBase { }

public class MyFactory
{
    private readonly Func<Derived1> _factory1;
    private readonly Func<Derived2> _factory2;
    private readonly Func<Derived3> _factory3;

    public MyFactory(Func<Derived1> factory1, Func<Derived2> factory2, Func<Derived3> factory3)
    {
        _factory1 = factory1;
        _factory2 = factory2;
        _factory3 = factory3;
    }

    public AbstractBase Create()
    {
        if (AppSettings.ProductType == ProductType.Type1)
            return _factory1();

        if (AppSettings.ProductType == ProductType.Type2)
            return _factory2();

        if (AppSettings.ProductType == ProductType.Type3)
            return _factory3();

        throw new NotSupportedException();
    }
}


以及在DI容器中的注册:

Container container = new Container();
container.RegisterSingle<Derived1>();
container.RegisterSingle<Derived2>();
container.RegisterSingle<Derived3>();
container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());
container.RegisterSingle<Func<Derived2>>(() => container.GetInstance<Derived2>());
container.RegisterSingle<Func<Derived3>>(() => container.GetInstance<Derived3>());
container.RegisterSingle<MyFactory>();

container.Verify(); // <-- will instantiate new instances of Derived1, Derived2, and Derived3. Not desired.

最佳答案

根据您的问题,我知道您非常了解当前方法的弊端,并且当前正在处理一些无法在单个迭代中更改的旧代码。但是由于其他人也会读这篇文章,所以我想通常记下injection constructors should be simple, fast and reliable

这样一来,就可以回答问题:不,没有办法将注册标记为跳过在Simple Injector中注册的方式。

在调用InstanceProducer之前为容器创建的所有Verify()实例都将得到验证(除非之前已对其进行垃圾回收)。通常,在调用InstanceProducer重载时会为您隐式创建Register实例,但是您也可以通过调用InstanceProducer创建新的Lifestyle.CreateProducer实例。这些生产者将不属于任何对象图(这意味着Simple Injector不会使用它们来自动关联其他类型),它们将充当根类型。但是,容器仍会跟踪这些生产者,并且验证也将应用于它们。

因此,这里的技巧是在验证过程之后触发创建新的InstanceProducer实例。您可以通过调用Lifestyle.CreateProducer来执行此操作,也可以通过解析未注册的具体类型来执行此操作,如您在示例中所做的那样。解决未注册类型的缺点当然是默认情况下将其解析为瞬态。有两种解决方法。您可以自己缓存实例,也可以指示容器将特定类型创建为单例。

自己进行缓存可能看起来像这样:

var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);


但是,自己进行缓存有一个缺点,那就是您会误诊诊断系统,从而无法为您找到任何Lifestyle mismatches。因此,更好的选择是override the lifestyle selection behavior,如下所示:

// Custom lifestyle selection behavior
public class AbstractBaseDerivativesAsSingleton : ILifestyleSelectionBehavior {
    public Lifestyle SelectLifestyle(Type serviceType, Type implementationType) {
        typeof(AbstractBase).IsAssignableFrom(implementationType)
            ? Lifestyle.Singleton
            : Lifestyle.Transient;
    }
}

// Usage
var container = new Container();
container.Options.LifestyleSelectionBehavior =
    new AbstractBaseDerivativesAsSingleton();

container.RegisterSingle<Func<Derived1>>(() => container.GetInstance<Derived1>());


解决此问题的另一种方法是使用InstanceProducers调用自己创建Lifestyle.CreateProducer。由于需要单例,因此必须调用Lifestyle.Singleton.CreateProducer。这些生产者需要在调用之后创建以进行验证,因此您仍然需要使用Lazy作为延迟机制:

// This factory should be part of your composition root, 
// because it now depends on the container.
public class MyFactory : IMyFactory
{
    private readonly Container container;
    private readonly Dictionary<ProductType, Lazy<InstanceProducer>> producers;

    public MyFactory(Container container) {
        this.container = container;
        this.producers = new Dictionary<ProductType, Lazy<InstanceProducer>>
        {
            {ProductType.Type1, new Lazy<InstanceProducer>(this.CreateProducer<Derived1>)},
            {ProductType.Type2, new Lazy<InstanceProducer>(this.CreateProducer<Derived2>)},
            {ProductType.Type3, new Lazy<InstanceProducer>(this.CreateProducer<Derived3>)}
        };
    }

    public AbstractBase Create() {
        return (AbstractBase)this.producers[AppSettings.ProductType].GetInstance()
    }

    private InstanceProducer CreateProducer<T>() where T : AbstractBase {
        Lifestyle.Singleton.CreateProducer<AbstractBase, T>(this.container);
    }
}


还可以考虑将您的工厂更改为调解员或代理。工厂通常是not the right abstraction,因为它们通常只会增加复杂性。相反,您可以制作一个具有相同接口的代理,并将调用委派给实际实例(在该实例中,您仍在后台使用类似于工厂的行为):

public class AbstractBaseAppSettingsSwitchProxy : AbstractBase
{
    private readonly IMyFactory factory;
    public AbstractBaseAppSettingsSwitchProxy(IMyFactory factory) {
        this.factory = factory;
    }

    public override void SomeFunction() {
        this.factory.Create().SomeFunction();
    }
}


使用此代理,您可以使任何消费者都看不到存在多种可能的AbstractBase实现的事实。消费者可以简单地与AbstractBase进行通信,就好像总是有一个。这可以使您的应用程序代码更简洁,使用者代码更简单,并使使用者更易于测试。

如果没有代理服务器,您仍然可以考虑使用中介者模式。中介器的工作原理与上述代理大致相同,但区别在于它具有自己的接口。因此,这很像工厂(具有自己的接口),但是区别在于调解器负责调用委托的对象。它不会将实例返回给使用者。

我知道,由于AbstractBase的结构,这些解决方案可能不适用于您。如果您有一个胖的基类,并且其中的某些方法是虚拟的,而另一些则没有,那么这样做可能会非常困难。这些解决方案通常只能在设计良好(SOLID)的系统中很好地工作。但这实际上就是所有这些。我的经验是,没有SOLID代码,一切都会变得很麻烦。作为软件开发人员,我们的主要工作之一是降低软件的总体拥有成本,而做到这一点的最佳方法之一是将SOLID原理应用于我们的软件。

最后一点。在我看来,您正在读取工厂内部的一些配置值。如果在应用程序的配置文件中定义了该值,则只能通过重新启动应用程序来更改该值(这是IIS自动为您完成的操作)。如果这是这样的配置值,那么实际上您根本不需要所有这些废话。您可以简单地进行以下注册:

Container container = new Container();

container.RegisterSingle(typeof(AbstractBase, GetConfiguredAbstractBaseType()));

private static Type GetConfiguredAbstractBaseType() {
    switch (AppSettings.ProductType) {
        case ProductType.Type1: return typeof(Derived1);
        case ProductType.Type2: return typeof(Derived2);
        case ProductType.Type3: return typeof(Derived3);
        default: throw new NotSupportedException();
    }
}


当然,这使我们再次回到无法验证的最初问题。但是我们可以再次使用代理解决此问题:

public class LazyAbstractBaseProxy : AbstractBase
{
    private readonly Lazy<AbstractBase> lazy;
    public LazyAbstractBaseProxy(Lazy<AbstractBase> lazy) {
        this.lazy = lazy;
    }

    public override void SomeFunction() {
        this.lazy.Value.SomeFunction();
    }
}

Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<AbstractBase>(new LazyAbstractBaseProxy(
    new Lazy<AbstractBase>(() => (AbstractBase)lazy.Value.GetInstance()));


而且如果不可能,您甚至可以跳过工厂,直接将Func注入消费者:

Type type = GetConfiguredAbstractBaseType();
var lazy = new Lazy<InstanceProducer>(() =>
    Lifestyle.Singleton.CreateProducer(typeof(AbstractBase), type, container));
container.RegisterSingle<Func<AbstractBase>>(() => (AbstractBase)lazy.Value.GetInstance());

关于c# - 简单注入(inject)器:如何跳过对容器中对象的验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31688864/

相关文章:

c# - 是否可以在 Windows Phone 7 文本框中放置 "hints"?

c# - Windows 上的 Java 需要记录 CPU 负载和类似的操作系统特定性能信息

c# - 如何在 WCF 服务中注入(inject)依赖项

c# - 使用简单注入(inject)器解析具有相同类和接口(interface)的多个对象

configuration - 带有简单注入(inject)器的遗留 .NET 应用程序中的选项模式、配置

c# - 在 WPF DataGrid Row 中禁用焦点

ruby - Ruby 中依赖注入(inject)的最佳实践是什么?

Android Dagger 依赖注入(inject)在私有(private)字段上失败

c# - Ninject 等效于 SimpleInjector RegisterDecorator 方法

c# - 可滚动图片框c#.net使用双缓冲停止闪烁