ASP.NET Core依赖注入系统学习教程:容器对构造函数选择的策略
.NET Core的依赖注入容器之所以能够为应用程序提供服务实例,这都归功于ServiceDescriptor对象提供的服务注册信息。另外,在ServiceDescriptor对象中,还为容器准备了3种提供服务实例的方式:
- 使用Func<IServiceProvider, object>类型的委托对象作为工厂进行提供;
- 直接使用实例化的对象进行提供;
- 根据服务的实现类型实例化进行提供;
如果容器选择“根据服务的实现类型实例化进行提供”(上面的第3种方式)作为提供服务实例的方式,那么容器就必须调用实现类型的构造函数才能创建相应类型的实例。在大部分的应用程序中,实现类型必然会对其他类型产生依赖,并且依赖的类型会存在多个,这就使实现类型为了初始化依赖的对象而定义多个构造函数。那么对于这样的情况而言,必然会产生一个问题:当容器发现实现类型中存在多个构造函数时,它会选择哪一个构造函数来创建服务实例?

其实我们可以将容器对构造函数选择的策略看作:使用两个条件在多个构造函数中筛选的过程。最终同时满足两个条件,筛选出的唯一构造函数,就是容器用于创建服务实例的构造函数,这两个条件如下:
- 构造函数中的参数类型,必须都进行了服务注册;
- 构造函数中的参数列表,是所有构造函数的超集;
为了使读者能够更加深刻地理解策略中的两个条件,下面我们会根据示例演示的方式进行讲述。
示例背景
本示例将定义4个服务接口(IServiceA、IServiceB、IServiceC和IServiceD),以及实现这个4个接口的类型(ServiceA、ServiceB、ServiceC和ServiceD)。示例将选择ServiceD类型作为切入点,所以容器将会在ServiceD类型的多个构造函数中选择一个。其他的几个类型将作为ServiceD类型的依赖项,并将它们通过ServiceD的构造函数对其初始化。
1 public interface IServiceA { }
2 public interface IServiceB { }
3 public interface IServiceC { }
4 public interface IServiceD { }
5
6 public class ServiceA: IServiceA { }
7 public class ServiceB : IServiceB { }
8 public class ServiceC : IServiceC { }
9 public class ServiceD : IServiceD
10 {
11 private IServiceA _serviceA;
12 private IServiceB _serviceB;
13 private IServiceC _serviceC;
14 private IServiceD _serviceD;
15 public ServiceD(IServiceA serviceA)
16 {
17 _serviceA = serviceA;
18 Console.WriteLine("选择的是构造函数是:ServiceD(ServiceA serviceA)");
19 }
20
21 public ServiceD(IServiceA serviceA, IServiceB serviceB)
22 {
23 _serviceA = serviceA;
24 _serviceB = serviceB;
25 Console.WriteLine("选择的是构造函数是:ServiceD(ServiceA serviceA, ServiceB serviceB)");
26 }
27 public ServiceD(IServiceA serviceA, IServiceB serviceB, IServiceC serviceC)
28 {
29 _serviceA = serviceA;
30 _serviceB = serviceB;
31 _serviceC = serviceC;
32 Console.WriteLine("选择的是构造函数是:ServiceD(ServiceA serviceA, ServiceB serviceB, ServiceC serviceC)");
33 }
34
35 }
在控制台的演示程序中创建了一个ServiceCollection对象,并在其中添加针对IServiceA、IServiceB、IServiceD这3个服务接口的服务注册,但未添加针对服务接口IServiceC的注册。然后使用ServiceCollection对象的BuildServiceProvider方法构建容器对象,并通过容器对象获取IServiceD的服务实例。
1 static void Main(string[] args)
2 {
3 var serviceCollextion = new ServiceCollection();
4 serviceCollextion.AddTransient<IServiceA,ServiceA>();
5 serviceCollextion.AddTransient<IServiceB, ServiceB>();
6 serviceCollextion.AddTransient<IServiceD, ServiceD>();
7
8 var provider = serviceCollextion.BuildServiceProvider();
9 provider.GetService<IServiceD>();
10
11 } // END Main()
分析
在定义和编写了示例中的代码后,在执行这个示例程序之前我们先使用策略中的第一个条件(构造函数中的参数类型,必须都进行了服务注册),在结合和“服务注册信息”。然后通过图例分析的方式来分析看看,容器会在ServiceD类型中筛选出哪些构造函数,使用第一个筛选条件的分析结果如下图:

从上图的分析内容可以看出,第三个构造函数被过滤掉了。这是因为第三个构造函参数列表的IServiceC类型并没有进行服务注册。另外,对于符合第一个筛选条件的构造函数,通常被称为“候选构造函数”。在获得第一个筛选条件的结果(候选构造函数列表)后,我们在使用第二个筛选条件(构造函数中的参数列表,是所有构造函数的超集)进行对其进行分析。
第二个筛选条件中的“超集”是筛选的核心,在这里超集的意思就是:超集构造函数的参数列表,会包含所有构造函数的参数列表。每个候选构造函数的参数列表,都属于超集构造函数参数列表的子集。
例如一个集合S2中的每个元素都在集合S1中,即便集合S1中可能存在S2中没有的元素,但S1中的元素始终都会包含S2中所有元素,对于这样的情况而言集合S1就是S2的一个超集。反过来,S2是S1的子集,S1是S2的超集。
有了对“超集”概念的理解,我们便可以看出在本示例的“候选构造函数”中,属于超集的构造函数是:ServiceD(IServiceA serviceA, IServiceB serviceB)。此时,完成对“容器对构造函数选择策略”的分析,我们可以判定:容器在面临ServiceD类型多个构造函数时,会选择使用其中第二个构造函数:ServiceD(IServiceA serviceA, IServiceB serviceB),来实例化对象。
接下来我们通过运行本示例的演示程序,发现运行结果和我们使用“容器对构造函数选择策略”分析的结果一致。

无超集
但是对于某些情况而言,如在使用第一个条件(构造函数所有的参数类型都进行了服务注册)筛选出了符合条件的“候选构造函数”之后,没有发现符合第二个条件(没有超集)的构造函数会发生怎么样的现象?
接下来为了验证这种情况会带来什么样的现象,我们将代码示例进行如下的改动:
1 using Microsoft.Extensions.DependencyInjection;
2 using Microsoft.Extensions.DependencyInjection.Extensions;
3 using System;
4 using System.Collections.Generic;
5 using System.Diagnostics;
6 namespace ConsoleApp1
7 {
8 public interface IServiceA { }
9 public interface IServiceB { }
10 public interface IServiceC { }
11 public interface IServiceD { }
12
13 public class ServiceA: IServiceA { }
14 public class ServiceB : IServiceB { }
15 public class ServiceC : IServiceC { }
16 public class ServiceD : IServiceD
17 {
18 #region 字段...
19 private IServiceA _serviceA;
20 private IServiceB _serviceB;
21 private IServiceC _serviceC;
22 private IServiceD _serviceD;
23 #endregion
24
25
26 public ServiceD(IServiceA serviceA, IServiceB serviceB)
27 {
28 _serviceA = serviceA;
29 _serviceB = serviceB;
30 Console.WriteLine("选择的是构造函数是:ServiceD(ServiceA serviceA, ServiceB serviceB)");
31 }
32 public ServiceD(IServiceA serviceA,IServiceC serviceC)
33 {
34 _serviceA = serviceA;
35 _serviceC = serviceC;
36 Console.WriteLine("选择的是构造函数是:ServiceD(ServiceA serviceA,ServiceC serviceC)");
37 }
38 }
39
40
41 internal class Program
42 {
43 static void Main(string[] args)
44 {
45 var serviceCollextion = new ServiceCollection();
46 serviceCollextion.AddTransient<IServiceA,ServiceA>();
47 serviceCollextion.AddTransient<IServiceB, ServiceB>();
48 serviceCollextion.AddTransient<IServiceC, ServiceC>();
49 serviceCollextion.AddTransient<IServiceD, ServiceD>();
50
51 var provider = serviceCollextion.BuildServiceProvider();
52 provider.GetService<IServiceD>();
53
54 } // END Main()
55
56 }
57 }
对于上面改动的示例而言,ServiceD类型所有构造函数上的参数类型虽然都进行了服务注册,即符合第一个筛选条件。但是并没有一个构造函数的参数列表,能够成为所有构造函数参数列表的超集,即不符合第二个筛选条件。接下来我们运行示例程序看看,当没有超集的构造函数时会发生什么样的后果。

如上图所示,在运行该示例程序后抛出了异常,其中的异常信息表示:无法从两个候选的构造函数中选择一个最优的来创建服务实例。这个异常也意味着并没有一个构造函数的参数列表,能够成为所有构造函数参数列表的超集。
总结
对于本篇文章讲解的主题——“容器对构造函数选择的策略”,我个人认为其中讲解的内容对于依赖注入框架而言是比较有实用性的。我们可以试想下,如果你没有了解“容器对构造函数选择的策略”,那么你在为类型定义构造函数时并不会遵循策略,这很可能会导致你的应用程序中的类型没有按预期方式实例化,或者出现无法实例化服务的异常现象。
所以为了稳妥的使用依赖注入框架,我们必须遵循“容器对构造函数选择的策略”,以此保证了应用程序所依赖的类型进行了服务注册,并且保证容器在面临多个构造函数选择时能够选出对应的“超集”。
ASP.NET Core依赖注入系统学习教程:容器对构造函数选择的策略的更多相关文章
- ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法
在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...
- asp.net core 依赖注入几种常见情况
先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...
- ASP.NET Core 依赖注入基本用法
ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...
- # ASP.NET Core依赖注入解读&使用Autofac替代实现
标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...
- 实现BUG自动检测 - ASP.NET Core依赖注入
我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...
- [译]ASP.NET Core依赖注入深入讨论
原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...
- ASP.NET Core依赖注入——依赖注入最佳实践
在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...
- 自动化CodeReview - ASP.NET Core依赖注入
自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...
- ASP.NET Core 依赖注入最佳实践——提示与技巧
在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...
随机推荐
- MySQL中读页缓冲区buffer pool
Buffer pool 我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理.我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其缓存起来,所以MyS ...
- FTPClient处理中文乱码问题,实测通过了
使用FTPClient 操作FTP时,遇到路径或文件名中文乱码问题: 其中的一种处理方式: 在new FTPClient()后,可以设置编码, ftpClient=new FTPClient( ...
- [学习笔记]使用Docker+Jenkin自动化流水线发布.Net应用
使用Docker容器方案可以快速安全地将项目部署到客户的服务器上,作为公司项目,需要解决两个问题: 1. 需要搭建一个私有的Docker仓库,以便安全的存储镜像 2. 需要一套自动化发布方案,实现代 ...
- 【FAQ】运动健康服务REST API接口使用过程中常见问题和解决方法总结
华为运动健康服务(HUAWEI Health Kit)为三方生态应用提供了REST API接口,通过其接口可访问数据库,为用户提供运动健康类数据服务.在实际的集成过程中,开发者们可能会遇到各种问题,这 ...
- windows和linux系统下测试端口连通性的命令
0. ping 1. telnet 2. ssh 3. curl 4. wget 5. tcping 6. 总结 本文地址: https://www.cnblogs.com/hchengmx/p/12 ...
- Linux服务器启动jstatd服务
Linux服务器启动jstatd服务 1.查找jdk所在目录 2.在jdk的bin目录下创建文件jstatd.all.policy touch jstatd.all.policy 3.写入安全配置 g ...
- 表达式的动态解析和计算,Flee用起来真香
前言 在很多项目中经常会出现需要动态解析表达式和计算的场景,比如一些自动审核规则,或者是一些变量的值通过维护的公式在运行过程中动态算出:由于场景需求,都需要比较灵活的配置对应的表达式,然后希望在需要的 ...
- 【python基础】第10回 周总结
路径 可以简单的理解为路径就是某个事物所在的具体位置(坐标) 1.相对路径:必须有一个参考系,就是相对于自己的目标文件的位置. 2.绝对路劲:不需要有参考系,是指文件在硬盘上真正存在的路径. 计算机五 ...
- .NET ORM框架HiSql实战-第一章-集成HiSql
一.引言 做.Net这么多年,出现了很多很多ORM框架,比如Dapper,Sqlsugar,Freesql等等.在之前的项目中,用到的ORM框架也大多数是这几个老牌的框架. 不过最近园子关于.NET ...
- 『现学现忘』Git后悔药 — 27、版本回退介绍
目录 1.什么版本回退 2.需要了解两个知识点 (1)HEAD是什么 (2)HEAD指针用法 3.git reflog命令介绍 1.什么版本回退 版本回退也可以叫回滚. 若修改过的文件,不仅添加到了暂 ...