我想要一些建议,解决依赖注入在‘间接注入’的上下文.请考虑下面的示例.

假设我有三个接口,即:IAdminServiceIEmailServiceIBillService.每个都有单一的实现类型,即AdminServiceEmailServiceBillService.

下面是一个IAdminService及其实现类型的示例.

public interface IAdminService 
{
    void DoSomething();
}

public class AdminService : IAdminService 
{
    IServiceA A { get; }

    public AdminService (IServiceA A)
    {
        this.A = A;
    }

    public void DoSomething(){
    {
        A.QuerySomething();
    }
}

最重要的是,我有一个IServiceManagement的抽象,它利用了延迟加载.请参见下文.

public interface IServiceManagement
{
    IAdminService AdminService{ get;  }
    IEmailService IEmailService{ get; }
    IBillService BillService { get; }
}

public sealed class ServiceManagement : IServiceManagement
{
    private readonly Lazy<IAdminService> _adminService;
    private readonly Lazy<IEmailService> _emailService;
    private readonly Lazy<IBillService> _billService;

    public ServiceManagement (
        IServiceA ServiceA,
        IServiceB ServiceB,
        IserviceC serviceC,
        ...)
    {
        _adminService = new Lazy<IAdminService>(() => new AdminService(ServiceA));
        _emailService = new Lazy<IEmailService>(() => new EmailService(...));
        _billService = new Lazy<IBillService>(() => new BillService(...));
    }

    public IAdminService AdminService => _adminService.Value;
    public IEmailService EmailService => _emailService.Value;
    public IBillService BillService => _billService.Value;
}

服务注册为作用域服务(Web API),请参阅下面的扩展方法.

public static IServiceCollection RegisterLogicServices(
    this IServiceCollection services)
{
    services.AddScoped<IServiceManagement, ServiceManagement>();

    //generic services like UnitOfWork for E.F. Core 等.
    services.AddScoped<IServiceA , ServiceA>();
    services.AddScoped<IServiceB , ServiceB>();
    services.AddScoped<IServiceC , ServiceC>();

    return services;
}

例如,为了在控制器级别使用IServiceManagement,我将IServiceManagement作为构造函数参数注入,允许我访问服务的所有公开方法,如下所示:

public DoSomethingController(IServiceManagement manager)
{
  _manager = manager
}

public IActionResult ExecuteSomething()
{
    _manager.AdminService.DoSomething();

    return Ok();
}

在这一点上,一切都运行得很好.

然而,我想知道的是,是否有可能在IAdminService中有IServiceManagement的范围,所以我想在IAdminServiceIServiceManagement中执行一些在IEmailService中的东西.

我可以想到的一种方法是使用IServiceProvider,并将provider.GetRequiredService<IServiceManagement>()作为构造函数的属性传入,如下所示

public ServiceManagement (
    IServiceA ServiceA,
    IServiceB ServiceB,
    IserviceC serviceC,
    IServiceProvider provider,
    ...)
{
    _adminService = new Lazy<IAdminService>(
        () => new AdminService(
            ServiceA,
            provider.GetRequiredService<IServiceManagement>()));
    _emailService = new Lazy<IEmailService>(() => new EmailService(...));
    _billService= new Lazy<IBillService>(() => new BillService(...));
}

我会对所有服务都这样做,因为可能会出现这样的情况:IAdminService调用IEmailService中的方法,IEmailService中的相同方法可能调用IBillService中的方法,依此类推.

我想知道的是,此用例使用IServiceProvider是一个有效的用例,例如,

  • 它在依赖项注入的上下文中有效吗?
  • 当我访问从IServiceProvider开始的任何内容时会遇到问题吗?
  • 如果调用树太大(或者因为它的作用域应该很好),这是否会导致性能下降?
  • 等.

让我们来看看极端的情况,假设我向后台服务注入了IServiceManagment,这会导致任何问题吗?

如果你有其他解决方案可以解决我的担忧,请提出来,我想尽可能多地了解它.

推荐答案

它在依赖项注入的上下文中有效吗?

不,很可能不是.

您的问题中缺少的是why,您需要延迟加载.这一点很重要,因为一般而言,注入构造器should be simple.使用简单的注入构造函数可以快速构建对象,并减少延迟加载的需要.如果没有懒惰的装载,IServiceManagement就没有必要在身边.这简化了您的解决方案,并消除了当前的问题:循环依赖.

但是,即使在不可能移除加载缓慢的依赖项的情况下,将依赖项包装在IServiceManagement抽象中也不是一个合适的解决方案.这样做会泄露实现细节:服务加载速度很慢.使用依赖项不应该意识到这一点.最重要的是,通过公开所有IServiceManagement‘S依赖项,您正在创建一个解决方案,在该解决方案中隐藏了类使用的依赖项的实际数量,并使测试类变得更加困难,因为您还必须在测试中管理IServiceManagement实现.最后,IServiceManagement将成为所有类都依赖的单一依赖项;就像Service Locator anti-pattern一样.

相反,使用代理实现是一种更优雅的解决方案.例如,在本例中,您实现了一个IAdminService实现,它允许延迟加载实际的IAdminService实现.例如:

public sealed class LazyAdminService : IAdminService
{
    private readonly Lazy<IAdminService> lazy;
    public LazyAdminService(Lazy<IAdminService> lazy) => this.lazy = lazy;

    public void DoSomething() => this.lazy.Value.DoSomething();
}

假设您使用MS.DI作为您的DI容器,您可以按如下方式连接:

services.AddScoped<AdminService>();
services.AddScoped<IAdminService>(c => new LazyAdminService(
    new Lazy<IAdminService>(() => c.GetRequiredService<AdminService>()));

这更加优雅,因为IAdminService的消费者没有AdminService需要延迟初始化的概念,并且一旦AdminService变得延迟初始化,他们就不需要任何改变.

Csharp相关问答推荐

使用yaml将Azure函数代码部署到FunctionApp插槽时出现问题(zip未找到)

编写DataAnnotations自定义验证器的多种方法

处理. netstandard2.0项目中HttpClient.SslProtocol的PlatformNotSupportedException问题""

如何在C#中删除一个特殊字符,如"使用Regex"

如果属性名为xyz,我需要使用System.Text.Json修改字符串类型的值""<>

. net依赖注入如何避免服务类中的新

C#.NET依赖项注入顺序澄清

无法通过绑定禁用条目

如何注册类使用多级继承与接口

单行上的ReSharper数据标注

C#-VS2022:全局使用和保存时的代码清理

什么时候接受(等待)信号灯?尽可能的本地化?

当使用Dapper映射DBNull时,我可以抛出异常吗?

在使用StringBuilder时,如何根据 colored颜色 设置为richTextBox中的特定行着色?

如何在C#中正确类型化带有泛型的嵌套类

使用可空引用类型时C#接口实现错误

为什么我在使用有效令牌的情况下仍未获授权?

如何在单击按钮后多次异步更新标签

与另一个对象位于同一位置的对象具有不同的变换位置

我应该使用IMhemyCache来存储承载令牌,还是应该为Azure函数中的401个错误实施Polly重试策略?