依赖注入官方文档:https://learn.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection
依赖注入的原理是——反射
如果想用依赖注入框架需要:
先用NuGet下载Microsoft.Extensions.DependencyInjection。
使用名称空间using Microsoft.Extensions.DependencyInjection;

依赖注入框架的简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
internal class Program {
static void Main(string[] args) {
var sc = new ServiceCollection();//创建容器
sc.AddScoped(typeof(IStudent),typeof(Student));//接口描述,绑定该接口的类型的描述
var sp = sc.BuildServiceProvider();
// ==============================以下不再有new操作符
IStudent student = sp.GetService<IStudent>();
student.Study();
}
}
interface IStudent {
void Study();
}
class Student : IStudent {
public void Study() {
Console.WriteLine("我要学习");
}
}

依赖注入的几个概念
服务:就是你需要的对象;
注册服务:对象不能凭空出现,需要先注册;
服务容器:负责管理注册的服务
查询服务:创建对象及关联对象;
对象生命周期:Transient(瞬态)、Scoped(范围内单例)、Singleton(单例);
可以指定服务类型和实现类型,或者服务类型和实现类型是同一类型
服务类型尽量用接口
微软的控制反转组件(DependencyInjection)包含服务定位器和依赖反转

服务定位器的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using Microsoft.Extensions.DependencyInjection;
namespace Test {
internal class Program {
static void Main(string[] args) {
//1、创建容器
ServiceCollection services = new ServiceCollection();
//2、注册服务
services.AddTransient<TestServiceImp1>();
//3、创建服务提供者(相当于服务定位器)
using (var sp = services.BuildServiceProvider()) {
//4、从提供者获取服务
TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();
t1.Name = "Test1";
t1.SayHi();
}
}
}
public interface ITestService {
public string Name { get; set; }
public void SayHi();
}
public class TestServiceImp1 : ITestService {
public string Name { get; set; }

public void SayHi() {
Console.WriteLine("hi");
}
}
public class TestServiceImp2 : ITestService {
public string Name { get; set; }

public void SayHi() {
Console.WriteLine("你好");
}
}
}

瞬时模式与单例模式Transient、Singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args) {
ServiceCollection services = new ServiceCollection();
//Transient
//services.AddTransient<TestServiceImp1>();
//Singleton
//services.AddSingleton<TestServiceImp1>();
using (var sp = services.BuildServiceProvider()) {
TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();
t1.Name = "Test1";
TestServiceImp1 t2 = sp.GetService<TestServiceImp1>();
t2.Name = "Test2";
Console.WriteLine(object.ReferenceEquals(t1, t2));//判断是否为同一对象
Console.WriteLine(t1.Name);
}
}

范围模式Scope

Scope
如果一个类实现了IDisposable接口,则离开作用域之后,容器会自动调用对象的Dispose方法
不要在长生命周期的对象中引用比它短的声明周期的对象
生命周期的选择:
如果类无状态(无属性,无成员变量),建议为Singleton;如果类有状态,且有Scope控制,建议为Scoped,因为通常这种Scope控制下的代码都是运行在同一线程中,没有并发修改的问题;使用Transient要谨慎

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static void Main(string[] args) {
ServiceCollection services = new ServiceCollection();
//Scoped
services.AddScoped<TestServiceImp1>();
//老大sp
using (var sp = services.BuildServiceProvider()) {
//老大创造了小弟scope1
//创建范围,这个范围就是using的代码块
using (IServiceScope scope1 = sp.CreateScope()) {
//在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
//想获取服务不能直接问老大要,应该问小弟scope1要
TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();
TestServiceImp1 t2 = scope1.ServiceProvider.GetService<TestServiceImp1>();
Console.WriteLine(object.ReferenceEquals(t1, t2));
}
//老大创造小弟scope2
//创建范围,这个范围就是using的代码块
using (IServiceScope scope2 = sp.CreateScope()) {
//在scope中获取Scope相关的对象,scope1.ServiceProvider而不是sp
//想获取服务不能直接问老大要,应该问小弟scope2要
TestServiceImp1 t1 = scope2.ServiceProvider.GetService<TestServiceImp1>();
TestServiceImp1 t2 = scope2.ServiceProvider.GetService<TestServiceImp1>();
Console.WriteLine(object.ReferenceEquals(t1, t2));
}
}
}

扩充

当服务类型与实现类型不一致时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static void Main(string[] args) {
ServiceCollection services = new ServiceCollection();
//泛型方法
services.AddScoped<ITestService, TestServiceImp1>();
services.AddScoped<ITestService, TestServiceImp2>();
//非泛型方法
//services.AddScoped(typeof(ITestService),typeof(TestServiceImp1));
//自定义单例对象
//services.AddSingleton(typeof(ITestService),new TestServiceImp1());
using (var sp = services.BuildServiceProvider()) {
//获取服务时,<>里面是注册类型而不是实现类型
//如果找不到服务就返回null
ITestService ts1 = sp.GetService<ITestService>();
//如果找不到直接抛异常而不是返回null
//ITestService ts1 = sp.GetRequiredService<ITestService>();
//非泛型方法,返回object,使用时需要手动转换
//ITestService ts1 = (ITestService)sp.GetService(typeof(ITestService));
//一个注册类型对应多个实现类型时,可以获取全部
//如果不用GetServices而用GetService就以最后一个添加进来的为准
IEnumerable<ITestService> tests = sp.GetServices<ITestService>();
foreach (ITestService test in tests) {
Console.WriteLine(test.GetType());
}
ts1.Name = "Test1";
ts1.SayHi();
Console.WriteLine(ts1.GetType());
}
}

依赖注入写法

依赖注入是有传染性的,如果一个类的对象是通过DI创建的,那么这个类的构造函数中声明的所有服务类型的参数都会被DI赋值;但如果一个对象是程序员手动创建的,那么这个类就和DI没有关系,它的构造函数中声明的服务类型参数就不会被自动赋值。
.Net的DI默认是构造函数注入
你只管声明,其余交给DI
降低模块之间的耦合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using Microsoft.Extensions.DependencyInjection;
namespace Test {
internal class Program {
static void Main(string[] args) {
ServiceCollection services = new ServiceCollection();
//日志服务或者配置服务需要替换只需要在这里更改就行了
services.AddScoped<Controller>();
services.AddScoped<ILog, LogImp1>();
services.AddScoped<IStorage, StorageImp1>();
//如果从本地读取配置
//services.AddScoped<IConfig, ConfigImp1>();
//如果从数据库读取配置
services.AddScoped<IConfig, DbConfig>();
using(var sp = services.BuildServiceProvider()) {
var c = sp.GetRequiredService<Controller>();
c.Test();
}
}
}
class Controller {
private readonly ILog log;
private readonly IStorage storage;
public Controller(ILog log, IStorage storage) {
this.log = log;
this.storage = storage;
}
public void Test() {
this.log.Log("开始上传");
this.storage.Save("asdfa", "1.txt");
this.log.Log("上传完毕");
}
}
interface ILog {
public void Log(string msg);
}
class LogImp1 : ILog {
public void Log(string msg) {
Console.WriteLine($"日志:{msg}");
}
}
interface IConfig {
public string GetValue(string name);
}
class ConfigImp1 : IConfig {
public string GetValue(string name) {
return "SqlServer";
}
}
interface IStorage {
public void Save(string content,string name);
}
//如果从本地配置文件读取配置
class StorageImp1 : IStorage {
private readonly IConfig config;
public StorageImp1(IConfig config) {
this.config = config;
}
public void Save(string content, string name) {
string server = config.GetValue("server");
Console.WriteLine($"向服务器{server}的文件名为{name}上传{content}");
}
}
//如果从数据库读取配置
class DbConfig : IConfig {
public string GetValue(string name) {
return "MySql";
}
}
}