最通俗易懂的依赖注入之服务注册与注入

推荐关注「码侠江湖」星标,时刻不忘江湖事

这篇文章是 ASP.NET 6 依赖注入系列文章的第 4 篇,点击上方蓝字可以阅读整个系列。

在上一篇文章中,我们讨论了依赖注入的服务容器与服务作用域。

接下来,在这篇文章中,我们继续深入了解服务注册与注入相关的内容。

1d41557e071571aeca72f603bb93344c.png

服务注册

现在,让我们回头看一看ServiceCollection服务集合类型。

我们现在已经知道,根容器是通过调用服务集合的BuildSerivceProvider扩展方法创建的。

服务集合ServiceCollection对象是一个存放服务注册信息的集合。

以下是它的部分源码,完整代码在这里。

public class ServiceCollection : IServiceCollection
{private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();private bool _isReadOnly;public int Count => _descriptors.Count;public ServiceDescriptor this[int index]{get{return _descriptors[index];}set{CheckReadOnly();_descriptors[index] = value;}}// ...
}

通过这部分代码可以发现,ServiceCollection本质上是一个元素类型为ServiceDescriptor的集合。

ServiceDescriptor 是服务描述类,它描述的是某个注册服务的基本信息,其中包含了服务类型本身以及它的实现类型,还有生命周期模式。

服务注册的本质就是创建相应的服务描述对象,并将其添加到服务集合对象中的过程。

依赖注入系统就是利用服务描述信息,才能够提供我们所依赖的服务实例,并注入其中。

依赖注入系统就会根据指定的服务类型从服务集合中,找到相应的服务描述对象,并且根据它提供的类型信息,来创建服务实例。

构造函数注入

依赖注入系统在创建服务实例时会先调用它的构造函数。

如果构造函数有参数,那么传入构造函数的所有参数必须也必须先进行初始化。

所以,服务实现类的构造函数必须具备一个基本的条件,那就是服务容器能够提供构造函数所需的所有参数,这就是构造函数注入。

如果当我们的服务实现类有多个重载版本的构造函数时,那么依赖注入系统会如何进行选择呢?

比如下面这个示例:

public static class Sample
{public interface IAccount{ }public interface IMessage{ }public interface ITool{ }public interface ITest{ }public class Account: IAccount{}public class Message: IMessage{}public class Tool: ITool{}public class Test: ITest{public Test(IAccount account){Console.WriteLine($"Ctor:Test(IAccount)");}public Test(IAccount account, IMessage message){Console.WriteLine($"Ctor:Test(IAccount,IMessage)");}public Test(IAccount account, IMessage message, ITool tool){Console.WriteLine($"Ctor:Test(IAccount,IMessage,ITool)");}}public static void Main(){var test = new ServiceCollection().AddTransient<IAccount, Account>().AddTransient<IMessage, Message>().AddTransient<ITest, Test>().BuildServiceProvider().GetService<ITest>();}
}

在这个例子中,定义了 4 个服务接口IAccount IMessage ITool ITest以及它们的实现类。

其中Test类型中定义了 3 个构造函数,它们的每一个参数都是一个服务。

为了确定依赖注入系统,会选择哪个构造函数来创建服务实例,每个构造函数都会在控制台中输出自身的字符串标识。

Main 方法中创建了一个服务集合对象,并且注册了除ITool以外的其它 3 个服务接口。

那么当我们获取ITest的服务实例时,会发生什么情况呢?它会通过执行哪个构造函数创建实例呢?

我们可以尝试分析一下,对于定义在Test类中的 3 个构造函数来说,

由于我们只注册了IAccountIMessage服务接口,所以服务容器只能够提供给前两个构造函数的所有参数,

而第三个构造函数只有一个ITool类型的参数,服务容器无法提供。

根据前面所说的基本条件「服务容器能够提供构造函数所需的所有参数」,此种情况下,也只有Test类型的前两个构造函数是符合条件的。

那么在所有符合条件的构造函数中,依赖注入系统又会如何选择呢?

这里用一个专业的说法来描述:「如果某个构造函数的参数类型集合,能够成为所有合法构造函数参数类型集合的超集,那么这个构造函数就会被依赖注入系统选择。」

简单来说,就是在所有符合条件的构造函数中,选择参数最多的那个。

如果按照这两个条件来分析的话,那么应该是第二个构造函数,以下执行结果证明了这一点。

9072a26ab5770a94eb0e2af874953797.png

接下来,我们对上面的示例改动一下,修改了Test类型的构造函数:

public class Test: ITest
{public Test(IAccount account, IMessage message){Console.WriteLine($"Ctor:Test(IAccount)");}public Test(IMessage message, ITool tool){Console.WriteLine($"Ctor:Test(IAccount,IMessage)");}
}public static void Main()
{var test = new ServiceCollection().AddTransient<IAccount, Account>().AddTransient<IMessage, Message>().AddTransient<ITool, Tool>().AddTransient<ITest, Test>().BuildServiceProvider().GetService<ITest>();
}

我们只为Test类型定义了两个构造函数,它们都具有两个参数:

  • 一个构造函数的参数是 IAccount 和 IMessage 接口;

  • 另一个构造函数的参数是 IMessage 和 ITool 接口。

并且在 Main 方法中,将每个服务接口都进行了注册。

那么此种情况下,我们是否能成功获取一个ITest对象呢?如果可以的话,它又是通过执行哪个构造函数创建的呢?

虽然Test的两个构造函数的参数都可以由服务容器提供,并且也满足了第一个条件。

但是还有一个条件没有达成,那就是:没有一个构造函数的参数类型集合,能够成为所有合法构造函数类型集合的超集。

也就说,无法满足第二个条件,因此依赖注入系统无法就选择无能了。

运行这个示例,抛出如下的异常提示:无法从两个候选的构造函数中选择一个最优的来创建服务实例。

35a5096eb0a0ff65a92790286eea5b0e.png

注入方式

实际上,在依赖注入的世界中,除了构造函数注入以外,注入方式还有很多种:如属性注入、方法注入、特性注入等。

「属性注入」,就是服务类把所依赖的其它服务,以属性方式声明,并以属性方式注入进来。

依赖注入系统在实例化服务类时,会通过属性把服务类所需要的对象注入进来。

「方法注入」,就是当服务类中的某个方法的参数依赖其它服务时,以参数形式注入进来。

「特性注入」,可以说是其它注入方式的一种补充。

比如有多个构造函数都符合注入的标准条件,但你只想让其中一个构造函数拥有被注入的能力,这时候可以通过特性来修饰这个构造函数,指定它是唯一可注入的构造函数。

目前,.NET 6 的依赖注入系统原生只支持构造函数注入,不过在某些类型的 Web 应用扩展下,也支持特定的方法注入、特性注入、甚至属性注入。

更多精彩内容,请关注我▼▼

5f9c062fea7608622018437840658441.gif

如果喜欢我的文章,那么

在看和转发是对我最大的支持!

(戳下面蓝字阅读)

433edb364d66f8d5088baf51ed07d708.png

推荐关注微信公众号:码侠江湖

                        e9a094f6a732c9aada82a83a248ff135.png觉得不错,点个在看再走哟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/288736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux之可视化查看磁盘大小并且删除大文件

1 问题 可视化查看磁盘大小并且删除大文件&#xff0c;之前我一直没有找到好的办法&#xff0c;原谅我的无知。 2 操作 我们直接搜索Disk Usage Analyzer 然后一个一个目的的点击查看&#xff0c;如下图 我们就可以清晰的看到文件占用大小&#xff0c;我们就可以很好的删除了…

php Heredoc应用说明

Heredoc部分实现界面与代码的准分离 我们如下的例子可以了解 Heredoc&#xff1a; <?php $name http://blog.csdn.net/a757291228; echo <<<HRERDOC <html> <head> <title>http://blog.csdn.net/a757291228</title> </head>…

C#委托、事件学习之(一)——委托事件描述

1、委托是一个类,定义了方法的类型,使得该方法可以作为其他方法的参数。避免了if...else的大量使用。 using System;namespace ConsoleApplication1 {public class GreetPeople{public delegate void GreetEventHander(object sender, GreetEventArgs e);public event Greet…

个人空间风格模版php,ThinkPHP 模板布局

模块继承 {extend name"" /}{block name""}{/block}基础模板 base.html子模板 块区在子模板中&#xff0c;可以对基础模板中的区块进行重载定义&#xff0c;如果没有重新定义的话&#xff0c;则表示沿用基础模板中的区块定义&#xff0c; 如果定义了一个空…

Win10用户远超4亿 Win10 RS2明年年初发布

10月26日晚22:00&#xff0c;微软Win10新品发布会在纽约芝士举行。发布会前&#xff0c;微软宣布目前最新的操作系统Windows 10已经迎来了4亿的用户&#xff0c;Windows 10的使用小时数&#xff0c;已经达到了2000亿&#xff0c;游戏时间也增长了500%。 同时微软还宣布&#xf…

bootstrap导航

HTML <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IE-edge"><!-- 开启IE8渲染模式 --><meta name"viewport" cont…

一个类可以实现多个接口吗_Java入门:基础知识(面向对象:接口)

接着上一篇的基础知识&#xff0c;今天俺们来学习一下&#xff1a;面向对象(万物皆对象)三大特征(封装、继承、多态)接口接口 概述&#xff1a;接口是一种引用数据类型&#xff0c;是方法的集合&#xff0c;接口的内部主要是定义方法&#xff0c;包含常量、抽象方法(JDK7及以…

剑指offer之判断链表是否包含环

1 问题 判断链表是否包含环 2 思路 2个指针&#xff0c;一个指针走一步&#xff0c;一个指针走2步&#xff0c;如果相遇则有&#xff0c;反之无。 3 代码实现 #include <stdio.h> #include <stdlib.h>#define true 1 #define false 0;typedef struct node {int …

生成条形码二维码DataMatrix条码.EAN码.39码.交叉25码.UPC码.128码.93码.ISBN码.Codabar等...

1.引用Spire.Barcode在Nuget包中安装Spire.Barcode2.生成条形码//创建 BarcodeSettings对象BarcodeSettings settings new BarcodeSettings();//设置条形类型为EAN-13settings.Type BarCodeType.EAN13;//设置条形码数据settings.Data "58465157484";//使用校检set…

php Trait 基础应用讲解

首先&#xff0c;上手册&#xff1a;自 PHP 5.4.0 起&#xff0c;PHP 实现了一种代码复用的方法&#xff0c;称为 trait。 随后。。。来讲解trait的用法&#xff1a; trait first_trait {function first_trait() {//里面写你想写的代码}function second_trait() {//里面写你想…

用UIpickView实现省市的联动

#import <UIKit/UIKit.h> interface ViewController : UIViewController<UIPickerViewDataSource,UIPickerViewDelegate> property(strong,nonatomic)UIPickerView *pickView; //定义一个可变数组用于存放省的数据 property(strong,nonatomic)NSMutableArray *Stat…

encodingaeskey java,消息体签名与加解密-开发者QA

Q 为什么要上线消息加密功能&#xff1f;A 为了更好的保护用户和公众账号的信息安全。Q 接入消息加解密功能复杂吗&#xff1f;A 开发者接入消息加解密功能并不复杂&#xff0c;微信团队提供了5种语言的示例代码(包括C、php、Python、Java和C#)&#xff0c;对于使用这个5种语言…

linux之lsof和netstat判断端口(port)被哪些应用占用

1 问题 判断端口&#xff08;port&#xff09;被哪些进程占用,我们要记得使用lsof&#xff08;list open file&#xff09;命令,或者用netstat命令。 2 命令 lsof -i:port netstat -apn | grep port 3 扩展 1) 列出所有tcp 网络连接信息 lsof -i tcp 2) 列出所有udp网络连…

多云战略:企业如何精益求精?

随着为企业用户提供更多的选择和更高的灵活性&#xff0c;多云模式正在开始受到他们的关注。但正如实施一个单一的云部署一样&#xff0c;企业需要为多云计算实施进行精心评估&#xff0c;从而选择正确的云服务供应商。在某些情况下&#xff0c;那些市场的非主流供应商可能会为…

python函数返回多个值时的数据类型是_Python3 注释多个返回值的函数类型

场景这要是讲函数注释的用法没有返回值def function(ver: str):print(var)单个返回值def function(ver: str) -> dict:a[ver,ver,ver]return a多个返回值您总是返回一个对象&#xff1b;使用return one, two只返回一个元组。所以是的&#xff0c;-> Tuple[bool, str] 完全…

最常用的动态sql语句梳理Mybatis(转)

公司项目中一直使用Mybatis作为持久层框架&#xff0c;自然&#xff0c;动态sql写得也比较多了&#xff0c;最常见的莫过于在查询语句中使用if标签来动态地改变过滤条件了。Mybatis的强大特性之一便是它的动态sql&#xff0c;免除了拼接sql带来的各种麻烦&#xff0c;在开发项目…

php 之 Final 关键字

本来想写匿名类的。。。不过是php7才支持&#xff0c;相比大家可能版本一般不会那么高&#xff0c;所以我就不写了。 引用手册中的话&#xff1a; PHP 5 新增了一个 final 关键字。如果父类中的方法被声明为 final&#xff0c;则子类无法覆盖该方法。如果一个类被声明为 fina…

C#链式编程

前言昨天我写了一段这样的一段代码。var email Email.From("1075094220qq.com").To("1075094220qq.com").CC("1075094220qq.com").Subject("邮件标题").Body("<h1 align\"center\">黑哥</h1><p>黑哥…

C#动态链接库的创建及使用

动态链接库( Dynamic Link Library 或者 Dynamic-link Library,缩写为 DLL),是微软公司在微软Windows操作系统中,实现共享函数库概念的一种方式。这些库函数的扩展名是 ”.dll"、".ocx"(包含ActiveX控制的库)或者 ".drv"(旧式的系统驱动程序…

用C++实现简单随机二元四则运算

让我们想看看二元四则运算都需要实现什么&#xff1a; &#xff08;1&#xff09; 定制题目数量 &#xff08;2&#xff09; 是否有乘除法 &#xff08;3&#xff09; 题目数值范围 &#xff08;4&#xff09; 加减有无负数 &#xff08;5&#xff09; 除法有无余数 &#xff0…