使用Microsoft.Extensions.DependencyInjection,我可以在提供额外的构造函数参数的同时解析类型并构造实例吗?

With Microsoft.Extensions.DependencyInjection, can I resolve the type and construct an instance while providing extra constructor parameters?

使用 Microsoft.Extensions.DependencyInjection,我可以在提供额外的构造函数参数的同时解析类型并构造实例吗?

我想做的事情很容易通过例子来说明。下面,在CreateSomethingWithContext中,我需要使用ActivatorUtilities.CreateInstance来调用参数化构造函数,但我事先不知道ISomething的具体类型。

我可以使用 serviceProvider.GetRequiredService 来解析类型并创建默认实例,但是我无法传递 context 参数。

我可以使用 factory version of AddTransient,但这会破坏已经使用 GetRequiredService 创建默认实例的代码。

下面,一个可能的错误(我不喜欢)是创建一个 Something 的冗余默认实例,只是为了找出它的类型并将其与 [=17] 一起传递给 ActivatorUtilities.CreateInstance =] 参数.

有更好的方法吗? 澄清一下,我无法控制实现 ISomething/Something 的实际库。

using System;
using Microsoft.Extensions.DependencyInjection;

namespace App
{
  public interface ISomething
  {
    void DoSomething() => Console.WriteLine(nameof(DoSomething));    
  }  
  
  public class Something: ISomething
  {
    public Something() =>
      Console.WriteLine($"{this.GetType().Name} created");

    public Something(object context) =>
      Console.WriteLine($"{this.GetType().Name} created with {context}");
  }

  static class Program
  {
    private static IServiceProvider BuildServices() => new ServiceCollection()
      .AddTransient<ISomething, Something>()
      .BuildServiceProvider();

    static ISomething CreateSomething(IServiceProvider serviceProvider) =>
      serviceProvider.GetRequiredService<ISomething>();

    static ISomething CreateSomethingWithContext(IServiceProvider serviceProvider, object context) 
    {
      // first I need an instance of ISomething, only to learn its concrete type for
      var something = serviceProvider.GetRequiredService<ISomething>();
      var type = something.GetType();  
      // now that I have the type, I can use ActivatorUtilities.CreateInstance
      var something2 = (ISomething)ActivatorUtilities.CreateInstance(
        serviceProvider, type, context);
      return something2;
    }

    static void Main()
    {
      var serviceProvider = BuildServices();
      CreateSomething(serviceProvider);
      CreateSomethingWithContext(serviceProvider, Guid.NewGuid());
    }
  }
}

就我个人而言,我同意你不想用 ActivatorUtilities.CreateInstance 来做这件事——尽管它会起作用,但如果构造函数的签名发生变化,你最终会遇到运行时错误,我敢肯定最好出现编译时错误。

为什么不 register a completely separate factory 使用上下文构建对象 - 保持 AddTransient() 注册不变,以便所有依赖默认构造函数的调用代码仍然有效,并在需要的地方使用新创建的工厂插入您的 context?

您要求的东西从根本上违背了依赖倒置原则的思想,而依赖倒置原则是依赖注入的基础。因此,您不太可能在常见的依赖注入容器中找到对此类内容的支持。

为了理解这一点,让我们考虑一下为什么实际上要将具体实现 Something 注册为其接口类型 ISomething。通过这样做,您允许组件依赖于 抽象 而不是具体类型。这减少了耦合,因为它允许您轻松地将实现与满足接口规范的其他东西交换。

这个想法是,只要它与接口兼容,你实际上并不想知道当你依赖 ISomething 时你会得到什么实现。这也意味着您不想知道具体实现本身可能具有哪些依赖项。相反,依赖组件将控制权交给外部系统,通常是 DI 容器,而只希望系统为您提供所需的东西。

现在,当将这个想法与 ActivatorUtilities.CreateInstance 进行比较时,可能会发现这个实用方法实际上只是服务定位器模式的语法糖,通常与依赖注入形成直接对比:而不是依赖于系统给你任何你依赖的东西,你现在可以主动从容器本身请求你的依赖。 ActivatorUtiltilies.CreateInstance 就是这么做的,同时还允许您传递不应从容器中解析的值。

让我们假设存在一种允许您执行 CreateInstance<ISomething>(additionalValues) 的方法。您现在要求容器为您提供 ISomething 的实现,但显然您非常了解它的具体实现,因此您能够说出您想要传递哪些参数来解决它。这与我上面的解释相矛盾,你通常(故意)不想知道具体的实现是什么。


相反,我建议您为此创建一个合适的工厂,它是为您 createSomething 制作的,并提供了正确键入的方式添加“上下文”数据:

public class SomethingFactory : ISomethingFactory
{
    private readonly SomeDependency _someDependency;
    public SomethingFactory(SomeDependency someDependency)
    {
        _someDependency = someDependency;
    }

    public ISomething Create()
        => new Something(_someDependency);

    public ISomething CreateContext(object context)
        => new Something(_someDependency, context);
}

那个工厂是为 Something 实现而创建的,所以它知道需要传递给 Something 构造函数的参数类型是非常好的,因为它有一个抽象 ISomethingFactory 本身,您仍然可以使用依赖注入而不是使用服务定位器模式来正确地做到这一点。