C# 简单注入器:如何跳过容器中对象的验证
我使用简单注入器通过构造函数注入将依赖项注入到我的对象中 对于所有派生自公共抽象基类的特定对象集,我将注入工厂而不是具体对象,以便在运行时确定应该注入哪个派生实例。工厂以单例的形式在容器中注册,派生实例也是如此 在将我的所有对象注册到DI容器后,我调用Container.Verify方法来验证我的配置。问题是,此方法的实现会创建每个注册到容器的类型的实例。这些派生实例的创建成本很高,而且它们的创建会产生副作用,这些副作用来自正在更新以使用DI的遗留代码。长期来看,我会消除副作用,但短期内不可行 如何告诉容器不要验证这些派生实例 我希望至少为调试构建保留Verify调用,但不能接受Verify调用实例化的这些实例。此外,他们需要注册一个单身生活方式,所以我不能不注册他们的容器 我提出的一个解决方案是不向容器注册派生对象,从而使它们不可验证,然后让Singleton Lifestyle Factory实现缓存它创建的第一个实例。它可以工作,但很脏,如果有人在其他地方显式请求派生类型的实例,则不太可能创建新实例,因为默认的生活方式是暂时的 下面是我的类结构示例:C# 简单注入器:如何跳过容器中对象的验证,c#,dependency-injection,simple-injector,C#,Dependency Injection,Simple Injector,我使用简单注入器通过构造函数注入将依赖项注入到我的对象中 对于所有派生自公共抽象基类的特定对象集,我将注入工厂而不是具体对象,以便在运行时确定应该注入哪个派生实例。工厂以单例的形式在容器中注册,派生实例也是如此 在将我的所有对象注册到DI容器后,我调用Container.Verify方法来验证我的配置。问题是,此方法的实现会创建每个注册到容器的类型的实例。这些派生实例的创建成本很高,而且它们的创建会产生副作用,这些副作用来自正在更新以使用DI的遗留代码。长期来看,我会消除副作用,但短期内不可行
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对我的案例非常有效,谢谢!