C# 简单注入器:如何跳过容器中对象的验证

C# 简单注入器:如何跳过容器中对象的验证,c#,dependency-injection,simple-injector,C#,Dependency Injection,Simple Injector,我使用简单注入器通过构造函数注入将依赖项注入到我的对象中 对于所有派生自公共抽象基类的特定对象集,我将注入工厂而不是具体对象,以便在运行时确定应该注入哪个派生实例。工厂以单例的形式在容器中注册,派生实例也是如此 在将我的所有对象注册到DI容器后,我调用Container.Verify方法来验证我的配置。问题是,此方法的实现会创建每个注册到容器的类型的实例。这些派生实例的创建成本很高,而且它们的创建会产生副作用,这些副作用来自正在更新以使用DI的遗留代码。长期来看,我会消除副作用,但短期内不可行

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

对于所有派生自公共抽象基类的特定对象集,我将注入工厂而不是具体对象,以便在运行时确定应该注入哪个派生实例。工厂以单例的形式在容器中注册,派生实例也是如此

在将我的所有对象注册到DI容器后,我调用Container.Verify方法来验证我的配置。问题是,此方法的实现会创建每个注册到容器的类型的实例。这些派生实例的创建成本很高,而且它们的创建会产生副作用,这些副作用来自正在更新以使用DI的遗留代码。长期来看,我会消除副作用,但短期内不可行

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

我希望至少为调试构建保留Verify调用,但不能接受Verify调用实例化的这些实例。此外,他们需要注册一个单身生活方式,所以我不能不注册他们的容器

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

下面是我的类结构示例:

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.

从您的问题中,我了解到您非常清楚当前方法的缺点,并且您目前正在处理一些无法在一次迭代中更改的遗留代码。但既然其他人也会读到这篇文章,我想做一个平常的记录

有了这些,我们要回答以下问题:不,并没有办法将注册标记为跳过,以便在简单注入器中注册

在调用Verify之前为容器创建的所有InstanceProducer实例都将得到验证,除非它们在调用Verify之前已被垃圾收集。通常,当您调用寄存器重载时,InstanceProducer实例是隐式为您创建的,但您也可以通过调用Lifestyle.CreateProducer来创建新的InstanceProducer实例。这些生产者不会成为任何对象图的一部分,这意味着Simple Injector不会使用它们自动连接其他类型,它们充当根类型。然而,容器仍然跟踪这些生产商,验证也将适用于他们

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

自己进行缓存可能如下所示:

var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);
也可以考虑将你的工厂改为调解人或代理人。工厂往往是如此,因为它们往往只是增加了复杂性。相反,您可以创建一个代理,该代理采用相同的接口,并将调用委托给真实实例,在该实例中,您仍然在后台使用类似工厂的行为:

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一样。这可以使您的应用程序代码更干净,使用者代码更简单,并且使使用者更易于测试

如果代理不是选项,您仍然可以考虑中介模式。中介的工作原理与上面的代理大致相同,但不同之处在于它有自己的接口。因此,它非常类似于具有自己接口的工厂,但不同之处在于中介器负责调用委托对象。它不会将实例返回给使用者

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

最后一个注意事项。在我看来,您正在工厂内读取一些配置值。如果此值是在应用程序的配置文件中定义的,则只能通过重新启动应用程序来更改该值,这是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());

从您的问题中,我了解到您非常清楚当前方法的缺点,并且您目前正在处理一些无法在一次迭代中更改的遗留代码。但既然其他人也会读到这篇文章,我想做一个平常的记录

有了这些,我们要回答以下问题:不,并没有办法将注册标记为跳过,以便在简单注入器中注册

在调用Verify之前为容器创建的所有InstanceProducer实例都将得到验证,除非它们在调用Verify之前已被垃圾收集。通常,当您调用寄存器重载时,InstanceProducer实例是隐式为您创建的,但您也可以通过调用Lifestyle.CreateProducer来创建新的InstanceProducer实例。这些生产者不会成为任何对象图的一部分,这意味着Simple Injector不会使用它们自动连接其他类型,它们充当根类型。然而,容器仍然跟踪这些生产商,验证也将适用于他们

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

自己进行缓存可能如下所示:

var lazy1 = new Lazy<Derived1>(container.GetInstance<Derived1>);
container.RegisterSingle<Func<Derived1>>(() => lazy1.Value);
也可以考虑将你的工厂改为调解人或代理人。工厂往往是如此,因为它们往往只是增加了复杂性。相反,您可以创建一个代理,该代理采用相同的接口,并将调用委托给真实实例,在该实例中,您仍然在后台使用类似工厂的行为:

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一样。这可以使您的应用程序代码更干净,使用者代码更简单,并且使使用者更易于测试

如果代理不是选项,您仍然可以考虑中介模式。中介的工作原理与上面的代理大致相同,但不同之处在于它有自己的接口。因此,它非常类似于具有自己接口的工厂,但不同之处在于中介器负责调用委托对象。它不会将实例返回给使用者

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

最后一个注意事项。在我看来,您正在工厂内读取一些配置值。如果此值是在应用程序的配置文件中定义的,则只能通过重新启动应用程序来更改该值,这是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());
您可以将Func注册到将延迟加载InstanceProducer的延迟服务器,如下所示:

private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);

public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
    where T : class
{
    var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
    container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}

Container container = new Container();

container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);

container.RegisterSingleton<MyFactory>();
您可以将Func注册到将延迟加载InstanceProducer的延迟服务器,如下所示:

private static Lazy<T> Lazy<T>(Func<T> func) => new Lazy<T>(func);

public static void RegisterDelayedFunc<T>(this Container container, Lifestyle lifestyle)
    where T : class
{
    var lazy = Lazy(() => lifestyle.CreateProducer<T, T>(container));
    container.RegisterSingleton<Func<T>>(() => lazy.Value.GetInstance());
}

Container container = new Container();

container.RegisterDelayedFunc<Derived1>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived2>(Lifestyle.Singleton);
container.RegisterDelayedFunc<Derived3>(Lifestyle.Singleton);

container.RegisterSingleton<MyFactory>();

回答主要问题:如何跳过容器中对象的验证

您可以这样做:

Container container = new Container();

Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => !container.IsVerifying,
    trueLifestyle: Lifestyle.Singleton,
    falseLifestyle: Lifestyle.Transient);

container.Register<TConcrete>(singletonLifestyle);
回应一本正经 问题:如何跳过容器中对象的验证

您可以这样做:

Container container = new Container();

Lifestyle singletonLifestyle = Lifestyle.CreateHybrid(
    lifestyleSelector: () => !container.IsVerifying,
    trueLifestyle: Lifestyle.Singleton,
    falseLifestyle: Lifestyle.Transient);

container.Register<TConcrete>(singletonLifestyle);

AppSettings.ProductType是否可以在运行时更改,或者它是需要重新启动应用程序的配置值?无法更改。这是一个需要重新启动应用程序的配置值。这很好,因为这使解决方案更容易。AppSettings.ProductType是否可以在运行时更改,或者是一个需要重新启动应用程序的配置值?它不能更改。这是一个需要重新启动应用程序的配置值。这很好,因为这使解决方案更容易。完全符合我要求的最终解决方案是“GetConfiguredStractBaseType”代码。谢谢你对我遇到的问题进行了透彻的分析。MarkSeemans博客的链接也很有用。我知道副作用很糟糕,但没有意识到注入依赖的“重量”也是我面临的真正问题。随着时间的推移,我将尝试简化这些类。代理实现不会很快变得不适吗?如果有人想向抽象基或派生实例添加/删除功能,则需要了解代理并手动更新。@Josh:是的,但这只是因为基类在特定方面违反了SOLID原则。如果您围绕狭窄的角色界面设计系统,那么问题就会完全消失。顺便说一句,系统的某些部分设置起来既慢又昂贵。通常你对此无能为力。但通常情况下,在构建对象的过程中仍然应该防止这种负载的发生。这些博客文章也是很好的阅读材料。你对违规行为的看法是对的,那些博客文章现在让我看得很清楚。可惜的是,我最初写了这些课程,不能怪别人;最后一个完全符合我要求的解决方案是“GetConfiguredAbstractBaseType”代码。谢谢你对我遇到的问题进行了透彻的分析。MarkSeemans博客的链接也很有用。我知道副作用很糟糕,但没有意识到注入依赖的“重量”也是我面临的真正问题。随着时间的推移,我将尝试简化这些类。代理实现不会很快变得不适吗?如果有人想向抽象基或派生实例添加/删除功能,则需要了解代理并手动更新。@Josh:是的,但这只是因为基类在特定方面违反了SOLID原则。如果您围绕狭窄的角色界面设计系统,那么问题就会完全消失。顺便说一句,系统的某些部分设置起来既慢又昂贵。通常你对此无能为力。但通常情况下,在构建对象的过程中仍然应该防止这种负载的发生。这些博客文章也是很好的阅读材料。你对违规行为的看法是对的,那些博客文章现在让我看得很清楚。可惜的是,我最初写了这些课程,不能怪别人;使用container.isverify对我的案例非常有效,谢谢!使用container.isverify对我的案例非常有效,谢谢!