.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. Clash 规则的写法

    这篇博文是针对 CFW 写的. 最近尝试从 v2 转向使用 Clash.基于一个简单的需求:用 Spotify 听专的时候用代理,用 AM 听专的时候直连,我参考了以下完成了我的规则: CFW 官网的 ...

  2. PostgreSQL Array 数组类型与 FreeSql 打出一套【组合拳】

    前言 PostgreSQL 是世界公认的功能最强大的开源数据库,除了基础数据类型 int4/int8/varchar/numeric/timestamp 等数据类型,还支持 int4[]/int8[] ...

  3. c++ 乘法逆元

    主要参考:OI-WIKI 为什么要逆元 当一个题目让你求方案数时常要取余,虽然 \((a+b)\% p=(a\% p+b\% p)\%p\) \((a-b)\% p=(a\% p-b\% p)\%p\ ...

  4. 2020.10.17【普及组】模拟赛C组 总结

    总结 这次比赛 120 分,老师说上 200 是不容易的,但我觉得这不是我真的水平 改题情况 T1 题目大意:有 N 个小朋友,每个小朋友有 \(B_i\) 个朋友,问从中随机选 3 人使得 3 人关 ...

  5. Python数据分析--Numpy常用函数介绍(9)--Numpy中几中常见的图形

    在NumPy中,所有的标准三角函数如sin.cos.tan等均有对应的通用函数. 一.利萨茹曲线 (Lissajous curve)利萨茹曲线是一种很有趣的使用三角函数的方式(示波器上显示出利萨茹曲线 ...

  6. 入坑KeePass(二)重置keepass设置

    保留好.kdbx和密钥文件,软件的文件可以删除掉,重新下载并解压设置就恢复默认了

  7. springboot整合ueditor实现图片上传和文件上传功能

    springboot整合ueditor实现图片上传和文件上传功能 写在前面: 在阅读本篇之前,请先按照我的这篇随笔完成对ueditor的前期配置工作: springboot+layui 整合百度富文本 ...

  8. VMware Workstation 虚拟机详细安装教程

    一.介绍篇 VMware Workstation 16 Pro是VMware(威睿公司)于2021年最新发布的一代虚拟机软件,软件的中文名是"VMware 工作站 16 专业版". ...

  9. CAP:多重注意力机制,有趣的细粒度分类方案 | AAAI 2021

    论文提出细粒度分类解决方案CAP,通过上下文感知的注意力机制来帮助模型发现细微的特征变化.除了像素级别的注意力机制,还有区域级别的注意力机制以及局部特征编码方法,与以往的视觉方案很不同,值得一看 来源 ...

  10. Gitlab + Gitlab runner + Window powershell

    需求说明 根据领导要求,要把python 项目移到Gitlab 进行管理,并利用Gitlab CI/CD 进行自动化测试,打包,部署.(听起来很简单吧) 比较头大,完全没有经验,python 也是刚上 ...