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. 这篇 ...
随机推荐
- vsftp 详解
1.默认配置: 1>允许匿名用户和本地用户登陆. anonymous_enable=YES local_enable=YES2>匿名用户使用的登陆名为ftp或anonymo ...
- CF1681F Unique Occurrences
题意:一棵树,问每条路径上只出现一次的值的个数的和. 思路: 显然想到考虑边贡献.每条边权下放到下面的哪个点.\(up_i\)为上面第一个点权等于它的点.我们需要一个子树内点权等于它的点(如果满足祖孙 ...
- 浏览器代理user-agent
两种方法: 法1:浏览器地址栏输入:about://version,然后复制用户代理: 如果法1不行,法2肯定可以. 法2:打开任意浏览器,输入任意网址,下面以火狐和百度网址为例来进行说明: 打开火狐 ...
- k8s client-go源码分析 informer源码分析(6)-Indexer源码分析
client-go之Indexer源码分析 1.Indexer概述 Indexer中有informer维护的指定资源对象的相对于etcd数据的一份本地内存缓存,可通过该缓存获取资源对象,以减少对api ...
- Kubernetes-23:详解如何将CPU Manager做到游刃有余
k8s中为什么要用CPU Manager? 默认情况下,kubelet 使用CFS配额来执行 Pod 的 CPU 约束.Kubernetes的Node节点会运行多个Pod,其中会有部分的Pod属于CP ...
- Pytorch实现波阻抗反演
Pytorch实现波阻抗反演 1 引言 地震波阻抗反演是在勘探与开发期间进行储层预测的一项关键技术.地震波阻抗反演可消除子波影响,仅留下反射系数,再通过反射系数计算出能表征地层物性变化的物理参数.常用 ...
- BUUCTF-乌镇峰会种图
乌镇峰会种图 16进制拖到底一看便知
- git 删除、合并多次commit提交记录
合并多次记录 1. git log找到要合并的记录的数量. 2. git rebase -i HEAD~5 将最上面一个的记录选为pack,下面记录都改为s. ================= 删除 ...
- NC24083 [USACO 2017 Dec P]Greedy Gift Takers
NC24083 [USACO 2017 Dec P]Greedy Gift Takers 题目 题目描述 Farmer John's nemesis, Farmer Nhoj, has N cows ...
- Contest
Contest 题目 链接 题目描述 \(n\) 支队伍一共参加了三场比赛. 一支队伍 \(x\) 认为自己比另一支队伍 \(y\) 强当且仅当 \(x\) 在至少一场比赛中比 \(y\) 的排名高. ...