C#/.NET Core中如何通过DI为同一类传参创建多实例
我定义了如下接口:
public interface ITest { void SayHello(); }
该接口的实现类代码如下:
public class Test : ITest { Test(string type) { // Do Something with type } public void SayHello() { // } }
当前我在依赖注入容器中的注册方式为:
services.AddScoped<ITest, Test>();
我需要创建两个Test类实例,分别传入不同的构造参数,对应实例化逻辑如下:
Test t1 = new Test("ABC"); Test t2 = new Test("XYZ");
请问如何通过.NET Core的依赖注入机制实现上述需求?
首先需要修正一个代码问题:你当前Test类的构造函数没有指定访问修饰符,默认为private,DI容器无法访问私有构造函数生成实例,需要先将构造函数改为public:
public Test(string type) { // Do Something with type }
你可以根据自己的使用场景选以下任意一种原生DI支持的实现方式:
方案1:直接注册多实例,通过集合注入
.NET Core原生DI支持同一服务类型注册多个实现,注入时解析IEnumerable<服务类型>即可拿到所有注册的实例,直接在注册时通过工厂委托传入构造参数即可:
// 注册传入"ABC"的实例 services.AddScoped<ITest>(sp => new Test("ABC")); // 注册传入"XYZ"的实例 services.AddScoped<ITest>(sp => new Test("XYZ"));
使用时直接注入IEnumerable<ITest>,按注册顺序即可拿到两个目标实例:
public class BusinessService { private readonly ITest _testAbc; private readonly ITest _testXyz; public BusinessService(IEnumerable<ITest> testInstances) { var testArray = testInstances.ToArray(); _testAbc = testArray[0]; // 对应new Test("ABC") _testXyz = testArray[1]; // 对应new Test("XYZ") } }
这种方式实现最简单,适合两个实例使用时不需要按名称区分、只需要按注册顺序获取的场景。Scoped生命周期下,同一个请求范围内获取到的两个实例都是固定的,符合生命周期规则。
方案2:封装工厂类,按需获取实例
如果不想靠注册顺序区分实例,可以自定义工厂封装实例创建逻辑,还可以借助ActivatorUtilities自动解析Test类的其他DI托管依赖,后续Test新增其他容器托管的构造参数时,不需要手动修改工厂创建逻辑:
首先定义工厂接口和实现:
public interface ITestFactory { ITest GetByType(string type); } public class TestFactory : ITestFactory { private readonly IServiceProvider _serviceProvider; // 工厂本身的依赖由DI容器自动注入 public TestFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public ITest GetByType(string type) { return type switch { "ABC" => ActivatorUtilities.CreateInstance<Test>(_serviceProvider, "ABC"), "XYZ" => ActivatorUtilities.CreateInstance<Test>(_serviceProvider, "XYZ"), _ => throw new NotSupportedException($"不支持的类型参数: {type}") }; } }
注册工厂到DI容器:
services.AddScoped<ITestFactory, TestFactory>();
使用时直接注入工厂,按需获取对应实例即可:
public class BusinessService { private readonly ITest _testAbc; private readonly ITest _testXyz; public BusinessService(ITestFactory testFactory) { _testAbc = testFactory.GetByType("ABC"); _testXyz = testFactory.GetByType("XYZ"); } }
这种方式灵活性最高,后续新增其他type的实例只需要修改工厂的分支逻辑即可,不需要调整注册代码。
方案3:定义细分服务接口,直接注入对应实例
如果两个实例的业务职责完全隔离,也可以定义两个继承自ITest的子接口,分别对应不同参数的实现,注入时直接指定具体子接口类型即可,不需要靠顺序或工厂判断:
// 定义细分接口 public interface ITestAbc : ITest {} public interface ITestXyz : ITest {} // 对应实现类,调用基类构造函数传固定参数 public class TestAbc : Test, ITestAbc { public TestAbc() : base("ABC") {} } public class TestXyz : Test, ITestXyz { public TestXyz() : base("XYZ") {} }
注册服务:
services.AddScoped<ITestAbc, TestAbc>(); services.AddScoped<ITestXyz, TestXyz>();
使用时直接注入对应子接口即可拿到目标实例:
public class BusinessService { private readonly ITest _testAbc; private readonly ITest _testXyz; public BusinessService(ITestAbc testAbc, ITestXyz testXyz) { _testAbc = testAbc; _testXyz = testXyz; } }
这种方式类型最安全,不需要做运行时的类型判断或顺序依赖,适合两个实例职责差异较大的场景,缺点是需要额外定义子接口和实现类。
内容的提问来源于stack exchange,提问作者Sooraj Bathery

