使用 DI,每次使用接口时创建一个新实例

With DI, create a new instance every time the interface is used

我有类似下面的代码。

inyected class 有一个状态,所以我想创建一个干净的 new instance class 每次调用时实现 INumberWriter。有办法吗?

public class NumberWriter() : INumberWriter{
   private int i = 1;
   public void Write(){
      ConsoleWrite(i++);
   }
}

public class SomeClass(){
   private INumberWriter writer;
   public SomeClass(INumberWriter writer){
      this.writer= writer;
   }

   public void AMethod(){
      for(int i=0;i<5;i++){
         writer.Write();// I want to see on console 11111 insted of 12345
      }
   }
}

当您在每次迭代中需要 一项新服务时,您肯定需要在每次迭代中创建 一项新服务。因此,在创建 SomeClass-实例时提供一次依赖是没有意义的,但是在每次调用 AMethod.

时提供依赖

您可以为您创建某种工厂 class 并让工厂在每次迭代时创建一个新实例:

public class WriterFactory
{
    public INumberWriter => new NumberWriter();
}

public class SomeClass
{
    private WriterFactory factory;
    public SomeClass(WriterFactory factory){
        this.factory = factory;
    }

    public void AMethod()
    {
        for(int i=0;i<5;i++)
        {
            var writer = this.factory().CreateNewWriter();
            writer.Write();
        }
    }
}

现在,您无需为您的 class 提供编写器,而是为 class 提供 工厂,后者本身会创建编写器。

这不是您的 DI 容器可以(或者可以说应该)为您解决的问题。将依赖项注入消费者是 DI 容器的工作。之后,消费者将依赖项存储在私有字段中,这意味着 - 只要消费者还活着 - 它就会重用相同的依赖项。

幸运的是,您正在为接口编程,这意味着您可以使用特殊的 Proxy implementation that can be placed inside your application's Composition Root 来解决这个问题。例如:

public class AlwaysNewNumberWriter : INumberWriter
{
    public void Write() => new NumberWriter().Write();
}

您现在可以按如下方式注册 AlwaysNewNumberWriter,而不是在容器中注册 NumberWriter

services.AddSingleton<INumberWriter, AlwaysNewNumberWriter>();

但是,此解决方案可能过于简单,因为 NumberWriter 可能有其自身的依赖项,需要从 DI 容器中解析。在这种情况下,您需要一个稍微复杂的解决方案:

public class DispatchingNumberWriter : INumberWriter
    where TNumberWriter : INumberWriter
{
    private readonly Func<INumberWriter> factory;
    
    public DispatchingNumberWriter(Func<INumberWriter> factory)
    {
        this.factory = factory;
    }

    public void Write() => this.factory.Invoke().Write();
}

这个 DispatchingNumberWriter 接受一个 Func<INumberWriter> 工厂委托,它会在每次调用其 Write 方法时调用它。这允许您注入此 DispatchingNumberWriter.

而不是将 NumberWriter 实现注入消费者

以下代码演示了如何将所有内容连接在一起:

services.AddTransient<NumberWriter>();

services.AddScoped<INumberWriter>(c =>
    new DispatchingNumberWriter(
        () => c.GetRequiredService<NumberWriter>()));

请注意最后一个片段的以下内容:

  • DispatchingNumberWriter 随附一个委托,该委托会回调 DI 容器以解析新的 NumberWriter。这允许 NumberWriter 由 DI 容器创建,它可能使用 Auto-Wiring 构造类型,即自动检测和注入其依赖项。
  • 因为NumberWriter是由DI Container创建的,所以注册为Transient;否则实例可能仍会被重用。
  • DispatchingNumberWriter 注册为 Scoped,这是此 class 最安全的生活方式,因为:
    • DispatchingNumberWriter 注册为 Singleton 将导致它的 c 参数和对其 GetRequiredService 方法的调用到容器的全局范围内的 运行。如果 NumberWriter 或其瞬态依赖项之一开始实现 IDisposable(这可能导致内存泄漏),或者 NumberWriter 与 [= 具有直接或间接依赖项,这可能会导致问题35=] 生活方式(这会使这些依赖性 Singletons)。
    • DispatchingNumberWriter 注册为 Transient 将产生预期的效果,除非它(意外地)直接或间接注入到 Singleton 消费者中。因为在那种情况下,您将获得与上一点中描述的相同的行为,其中 DispatchingNumberWriter 注册为 Singleton。尽管将 Scoped DispatchingNumberWriter 注入 Singleton 消费者时问题没有什么不同,但 MS.DI 阻止将 Scoped 依赖项注入 Singleton消费者,因为它是known pitfall来预防的。