C#依赖注入 - 注入一个参数为相同接口的接口

3

我在尝试使用依赖注入时遇到了一些麻烦,需要帮助。

我有一个服务IService在我的应用程序中以几种不同的方式实现。

ServiceA : IService { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IService { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IService serviceA, IService serviceB) {...} }

在我的startup.cs文件中,我会根据配置文件中的参数选择要使用的内容。就像这样:
var service = Configuration["AppConfig:Service:ID"];
switch(service) {
    case "A":
        services.AddTransient<IService, ServiceA>();
    case "B":
        services.AddTransient<IService, ServiceB>();
    case "C":
        // ??
}

我可以轻松地创建和使用服务A和服务B。在它们被创建之前,我会提前注入他们的依赖项A和B。问题出现在第三个服务上,我需要将前面两个服务注入到它中。我的问题是:最好的方法是什么?

是否有一种方法可以创建服务A和B的具体实现,但在它们的构造函数中以某种方式使用注入的依赖项A和B?我是否必须更改接口来使其工作?也许我必须改变ServiceC的构造函数,以获取A和B的具体实现?

更新:不知道这是否是最佳解决方案,但我最终采取了以下措施使其起作用。

...
case "C":
    services.AddTransient<ServiceA>();
    services.AddTransient<ServiceB>();
    services.AddTransient<IService>(s => 
        new ServiceC(
            s.GetService<ServiceA>(),
            s.GetService<ServiceB>()
    ));

增加有关服务C的更多细节。如果确实需要这两个具体实现,则应更改构造函数。否则,它将违反Liskov替换原则。修复开关,没有breaks,并且serviceA注册了2次。 - apocalypse
服务C的目标是使用服务A的实现,但如果服务A无法提供正确的结果,则回退到服务B的结果。 - mymemesarespiciest
创建一个工厂模式来提供所需的服务,并在需要时进行实例化。并非所有内容都需要由 DI() 处理。 - monstertjie_za
@mymemesarespiciest,你可能知道A和B代表什么,但是其他人,包括编译器,只看到一个IService而不知道它的含义。如果有两个具体实现,为什么不传递相同的实例?或者两个A实例?或者两个B实例? - Panagiotis Kanavos
1
即使您不直接使用 Polly,也应该了解它的工作原理以及如何解决您可能遇到的相同问题。@mymemesarespiciest - Panagiotis Kanavos
显示剩余4条评论
2个回答

1

另一种选择是引入另一个接口,这样您就可以同时注册ServiceC及其依赖项,而不会出现任何歧义或循环引用。

interface IService {}
interface IServiceImpl : IService {}

ServiceA : IServiceImpl { public ServiceA(IDependencyA A, IDependency B) {...} }
ServiceB : IServiceImpl { public ServiceB(IDependencyA A, IDependency B) {...} }
ServiceC : IService { public ServiceC(IEnumerable<IServiceImpl> services) {...} }

services.AddTransient<IServiceImpl, ServiceA>();
services.AddTransient<IServiceImpl, ServiceB>();
services.AddTransient<IService, ServiceC>();

是的,它会。DI通常会注入第一个注册类型。请注意,我将ServiceC的构造函数更改为IEnumerable<T>,因此它将接收所有服务实现。 - Jeremy Lakeman
我理解为什么ServiceC是没问题的。但在这个周末,我需要自己测试ServiceA和ServiceB的设置。 - Roar S.
顺便问一下:在测试时,我应该如何解决控制器中的ServiceA和ServiceB?通过注入“IServiceImpl”吗? - Roar S.
这个想法是将 IService 保持为“公共 API”,并且只在内部引用 IServiceImpl 作为实现细节。 - Jeremy Lakeman
我看到OP使用了services.AddTransient<ServiceA>(); services.AddTransient<ServiceB>();解决了这个问题,也许现在没有必要深入探究了。无论如何,现在我理解了你答案的逻辑,给你点赞 :-) 感谢你的时间Jeremy。敬礼 - Roar S.

0

应该有更好的解释为什么需要这样的依赖关系。

Microsoft.Extensions.DependencyInjection不像其他IoC容器(例如Castle.Windsor)一样提供了命名注册。

但是还有一种方法可以通过A和B的实例来注册ServiceC。

            services.AddTransient<ServiceA>();
            services.AddTransient<ServiceB>();
            services.AddTransient<IService>(serviceProvider => {
                return new ServiceC((IService)serviceProvider.GetRequiredService(typeof(ServiceA)), (IService)serviceProvider.GetRequiredService(typeof(ServiceB)));
            });

很有趣,你发了这个帖子,因为它几乎与我刚找到的解决方案完全相同,最终起作用了。唯一的区别是你不需要将其转换为IService,因为那些服务已经实现了该接口。 - mymemesarespiciest
啊哈,不错!顺便说一下,我在写作时不会刷新。而且“有趣”的是你这样想 :) 强制转换的唯一原因是 GetRequiredService 需要类型转换。 - Iqan Shaikh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接