.NET Core的依赖注入容器之所以能够为应用程序提供服务实例,这都归功于ServiceDescriptor对象提供的服务注册信息。另外,在ServiceDescriptor对象中,还为容器准备了3种提供服务实例的方式:

  1. 使用Func<IServiceProvider, object>类型的委托对象作为工厂进行提供;
  2. 直接使用实例化的对象进行提供;
  3. 根据服务的实现类型实例化进行提供;

如果容器选择“根据服务的实现类型实例化进行提供”(上面的第3种方式)作为提供服务实例的方式,那么容器就必须调用实现类型的构造函数才能创建相应类型的实例。在大部分的应用程序中,实现类型必然会对其他类型产生依赖,并且依赖的类型会存在多个,这就使实现类型为了初始化依赖的对象而定义多个构造函数。那么对于这样的情况而言,必然会产生一个问题:当容器发现实现类型中存在多个构造函数时,它会选择哪一个构造函数来创建服务实例?

其实我们可以将容器对构造函数选择的策略看作:使用两个条件在多个构造函数中筛选的过程。最终同时满足两个条件,筛选出的唯一构造函数,就是容器用于创建服务实例的构造函数,这两个条件如下:

  1. 构造函数中的参数类型,必须都进行了服务注册;
  2. 构造函数中的参数列表,是所有构造函数的超集;

为了使读者能够更加深刻地理解策略中的两个条件,下面我们会根据示例演示的方式进行讲述。

示例背景

本示例将定义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依赖注入系统学习教程:容器对构造函数选择的策略的更多相关文章

  1. ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法

    在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...

  2. asp.net core 依赖注入几种常见情况

    先读一篇注入入门 全面理解 ASP.NET Core 依赖注入, 学习一下基本使用 然后学习一招, 不使用接口规范, 直接写功能类, 一般情况下可以用来做单例. 参考https://www.cnblo ...

  3. ASP.NET Core 依赖注入基本用法

    ASP.NET Core 依赖注入 ASP.NET Core从框架层对依赖注入提供支持.也就是说,如果你不了解依赖注入,将很难适应 ASP.NET Core的开发模式.本文将介绍依赖注入的基本概念,并 ...

  4. # ASP.NET Core依赖注入解读&使用Autofac替代实现

    标签: 依赖注入 Autofac ASPNETCore ASP.NET Core依赖注入解读&使用Autofac替代实现 1. 前言 2. ASP.NET Core 中的DI方式 3. Aut ...

  5. 实现BUG自动检测 - ASP.NET Core依赖注入

    我个人比较懒,能自动做的事绝不手动做,最近在用ASP.NET Core写一个项目,过程中会积累一些方便的工具类或框架,分享出来欢迎大家点评. 如果以后有时间的话,我打算写一个系列的[实现BUG自动检测 ...

  6. [译]ASP.NET Core依赖注入深入讨论

    原文链接:ASP.NET Core Dependency Injection Deep Dive - Joonas W's blog 这篇文章我们来深入探讨ASP.NET Core.MVC Core中 ...

  7. ASP.NET Core依赖注入——依赖注入最佳实践

    在这篇文章中,我们将深入研究.NET Core和ASP.NET Core MVC中的依赖注入,将介绍几乎所有可能的选项,依赖注入是ASP.Net Core的核心,我将分享在ASP.Net Core应用 ...

  8. 自动化CodeReview - ASP.NET Core依赖注入

    自动化CodeReview系列目录 自动化CodeReview - ASP.NET Core依赖注入 自动化CodeReview - ASP.NET Core请求参数验证 我个人比较懒,能自动做的事绝 ...

  9. ASP.NET Core 依赖注入最佳实践——提示与技巧

    在这篇文章,我将分享一些在ASP.NET Core程序中使用依赖注入的个人经验和建议.这些原则背后的动机如下: 高效地设计服务和它们的依赖. 预防多线程问题. 预防内存泄漏. 预防潜在的BUG. 这篇 ...

随机推荐

  1. C# 类继承中的私有字段都去了哪里?

    最近在看 C++ 类继承中的字段内存布局,我就很好奇 C# 中的继承链那些 private 字段都哪里去了? 在内存中是如何布局的,毕竟在子类中是无法访问的. 一:举例说明 为了方便讲述,先上一个例子 ...

  2. 腾讯云Redis全面升级,性能提升400%,可用性高达5个9

    2022年6月,腾讯云Redis全新升级,发布高性能版本,单节点可提供50W+吞吐,性能是原生Redis的4倍.同时,腾讯云Redis推出全球复制功能,解决原生Redis诸多痛点问题,可用性升级高达9 ...

  3. 认识一下什么是JSP

    摘要:JSP,全称是Java Server Pages,即Java服务器页面,是由Sun Microsystems公司主导创建的一种动态网页技术标准. 本文分享自华为云社区<Java服务器页面- ...

  4. numpy中的np.round()取整的功能和注意

    numpy中的np.round()取整的功能和注意 功能 np.round() 是对浮点数取整的一个函数,一般的形式为 np.round(a, b),其中a为待取整的浮点数,b为保留的小数点的位数 注 ...

  5. Phantomjs实用代码段(持续更新中……)

    一.下载 下载链接二.解压安装包 直接解压即可三.配置环境变量 找到高级系统设置,打开它,出现以下图.点击环境变量. 分别点击编辑按钮 分别新建添加当初的解压路径,到bin文件夹.点击确定. 这样,环 ...

  6. Xshell缺失mfc110u.dll文件解决方案(有下载链接)

    解决方案 把下面两个文件都下载安装就可以了. 1.vcredist_x86.exe链接: https://pan.baidu.com/s/1njbNHdjqH6x34GQvj4BTBg提取码: pwq ...

  7. tableSizeFor

    HashMap tableSizeFor() /** Returns a power of two size for the given target capacity. 1.(不考虑大于最大容量的情 ...

  8. 如何手动解析vue单文件并预览?

    开头 笔者之前的文章里介绍过一个代码在线编辑预览工具的实现(传送门:快速搭建一个代码在线编辑预览工具),实现了css.html.js的编辑,但是对于demo场景来说,vue单文件也是一个比较好的代码组 ...

  9. 皮尔逊(Pearson)系数矩阵——numpy

    一.原理 注意 专有名词.(例如:极高相关) 二.代码 import numpy as np f = open('../file/Pearson.csv', encoding='utf-8') dat ...

  10. 全国30m精度二级分类土地利用数据

    ​数据下载链接:数据下载链接 引言 全国土地利用数据产品是以Landsat TM/ETM/OLI遥感影像为主要数据源,经过影像融合.几何校正.图像增强与拼接等处理后,通过人机交互目视解译的方法,将全国 ...