ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

.NET 中的依赖注入(三):依赖关系和构造函数发现规则

2022-01-17 01:01:00  阅读:193  来源: 互联网

标签:依赖 ExampleService AService Worker 实例 NET public 构造函数


本文示例代码,均采用 .NET 6,具体的代码可以在这个仓库 Articles.DI 中获取。

前面的文章中,我们提及了依赖注入的基本使用。我们使用了简单的案例,注册了 IMessageWriter 接口,以及编写了两个实现类 MessageWriterLoggingMessageWriter,但是它们二者都只有一个构造函数。如果我们注册服务时,实现类有多个构造函数时,容器该如何选择呢?

如何选择构造函数

我们可以直接写代码,来模拟这个场景。有一个服务 ExampleService,它有多个构造函数,这些构造函数所需参数的数量有多有少,同时需要的类型也各有不同,具体看下面的代码:

// https://github.com/alva-lin/Articles.DI/tree/master/WorkerService3
public class ExampleService
{
    public ExampleService() => Console.WriteLine("空的构造函数");

    public ExampleService(AService aService) =>
        Console.WriteLine("单参数构造函数:AService");

    public ExampleService(AService aService, BService bService) =>
        Console.WriteLine("双参数构造函数:AService, BService");

    public ExampleService(AService aService, CService cService) =>
        Console.WriteLine("双参数构造函数:AService, CService");
}

public class AService
{
    public AService() => Console.WriteLine("AService 实例化");
}

public class BService
{
    public BService() => Console.WriteLine("BService 实例化");
}

public class CService
{
    public CService() => Console.WriteLine("CService 实例化");
}

ExampleService 类有四个构造函数,分别依赖三个服务,我们在注册服务时,只注册 AServiceBService

IHost host = Host.CreateDefaultBuilder(args)
   .ConfigureServices(services =>
    {
        services.AddHostedService<Worker>();
        services.AddSingleton<ExampleService>();

        // 尝试注释(or 取消注释)下面的代码,形成不同组合,运行以查看输出结果
        services.AddSingleton<AService>();
        services.AddSingleton<BService>();
        // services.AddSingleton<CService>();
    })
   .Build();

await host.RunAsync();

public class Worker : BackgroundService
{
    private readonly ExampleService _exampleService;

    // 注入了 ExampleService 的实例,但是调用了它的哪个构造函数?
    public Worker(ExampleService exampleService)
    {
        _exampleService = exampleService;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 不执行任何操作
    }
}

上述代码的执行结果为

AService 实例化
BService 实例化
双参数构造函数:AService, BService

从结果可以看出,容器在实例化 ExampleService 类时,使用了第三个构造函数。对比所有的构造函数,第三和第四个构造函数所需的参数数量最多,而第四个构造函数需要 CService 类,我们并未在容器中注册这个服务,所以容器不会选择第四个构造函数。那么我们可以明白,容器选择构造函数的一部分规则:

规则1:构造函数所需的参数类型必须是在容器中注册过的;
规则2:尽可能选择参数最多的构造函数;

如果我们在注册服务时,将 CService 一起注册,再运行一遍,这时程序就会报错:

Unhandled exception. System.AggregateException:
    Some services are not able to be constructed
(Error while validating the service descriptor
    'ServiceType: Microsoft.Extensions.Hosting.IHostedService
    Lifetime: Singleton
    ImplementationType: WorkerService3.Worker':

    Unable to activate type 'WorkerService3.ExampleService'.
    The following constructors are ambiguous:
        Void .ctor(WorkerService3.AService, WorkerService3.BService)
        Void .ctor(WorkerService3.AService, WorkerService3.CService))
...

错误信息指出无法构建 ExampleService 类型,两个构造函数有歧义,无法选择。那么我们可以知道第三个规则:

规则3:如果同时存在多个满足前面规则的构造函数,则会抛出异常。

依赖关系图

上述代码中,Worker 类依赖 ExampleService 类,而 ExampleService 类又依赖其他类,形成一个链式的依赖,那么容器在实例化 Worker 类时,会根据找到它的构造函数,Worker 类只有一个构造函数,声明了需要一个 ExampleService 类型的示例,那么容器就继续实例化 ExampleService 类,找到它的构造函数,而 ExampleService 类有多个构造函数,容器会根据实际情况,选择最合适的一个。

本文的代码流程如下:

  1. 创建 HostBuilder 时,注册后台服务 Worker,以及其他服务(services.add...);
  2. 启动后台服务,即 Worker 类(await host.RunAsync();)
  3. 容器实例化 Worker 类,找到其构造函数,解析所需的参数。找到了 ExampleService 类;
  4. 容器实例化 ExampleService 类,找到它有多个构造函数;
  5. 从参数数量最多的构造函数开始,对比是否能满足其条件,筛选出最满足需求的一个;
  6. 选择第三个构造函数,实例化 AServiceBService,因为二者构造函数简单,直接生成即可;
  7. AServiceBService 实例,注入到 ExampleService 类,完成实例化;
  8. ExampleService 实例,注入到 Worker 类,完成实例化;

Worker 类到 ExampleService 类,再到 AServiceBService,这是一个树形依赖关系。而容器实例化 Worker 类时,根据这个依赖关系,依次深入,生成一个个依赖项,将其递归式的注入。

总结

容器在构建实例时,选择构造函数的规则如下:

规则1:构造函数所需的参数类型必须是在容器中注册过的;
规则2:尽可能选择参数最多的构造函数;
规则3:如果同时存在多个满足前面规则的构造函数,则会抛出异常。

在复杂程序中,容器会分析服务的依赖项。从依赖关系树的最深处开始,依次构建,重复注入,以一种递归的方式,将最终需要的服务构建出来。

参考链接

.NET 中的依赖关系注入

标签:依赖,ExampleService,AService,Worker,实例,NET,public,构造函数
来源: https://www.cnblogs.com/asjun/p/15811906.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有