02020211 .NET Core重难点知识11-依赖注入、.NET中DI服务注册、服务的生命周期、IDisposable接口示例
1. 依赖注入概念(视频Part2-24)
1.1 依赖注入概述
- 生活中的控制反转:自己发电和用电网的电。
- 传统控制 → 什么东西都是自己拼装,自己组装。
- 反转控制 → 我不需要知道怎么弄,我要这个东西的时候别人就给我了。
- 依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。
- 依赖注入简化模块的组装过程,降低模块之间的耦合度。
1.2 自己发电的代码
var connSetting = ConfigurationManager.ConnectionStrings["connStr1"]; // 从配置文件取出数据
string connStr = connSetting.ConnectionString; // 将数据转换为字符串
SqlConnection conn = new SqlConection(connStr); // 通过字符串连接数据库优点:什么东西都是自己搞,对于细节很清楚,自己可以做一些比较精细化的控制。缺点:要求自己对一切都非常清楚,连接字符串从哪里来,需要调用哪个类,构造函数是怎么样的,需要创建哪些对象。
1.3 控制反转的目的
- 从怎样创建XX对象 → 转为 → 我要XX对象。
- 两种实现方式:
- 服务定位器(ServiceLocator)
- 依赖注入。
// 实现方式1:服务定位器。如下是伪代码,理解即可。
IDbConnection conn = ServiceLocation.GetService<IDbConnection>();说明:
1. ServiceLocation类是服务定位器,它有一个GetService方法,IDbConection是连接对象
2. GetService方法需要IDbConection这个数据库连接对象。
—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—·—
// 实现方式2:依赖注入
class Demo
{public IDbConnection Conn {get; set;} // 声明一个IDbConnection类型的属性Conn。public void InsertDB(){IDbCommand cmd = Conn.CreateCommand();}
}说明:
1. 在Demo类中声明了一个Conn属性。
2. 在创建Demo类对象的时候,框架会自动给属性赋值。此时你不管这个值是怎么来的,只要创建了对象,那么框架就自动给属性赋值了。
2. .NET中DI服务注册(视频Part2-25)
2.1 DI几个概念
- 服务(Service)→ 对象。服务就是你管框架要的对象。
- 我要一个数据库操作对象。
- 我要一个文件操作对象。
- 注册服务 → 对象不是凭空而来的,你要一个对象,那么这个对象怎么来的呢?
- 在要对象之前,你需要先把这个对象注册进去,然后才能拿到这个服务。
- 服务容器 → 负责管理注册的服务。注册服务,要服务都通过服务容器来实现。
- 注册服务到服务容器,从服务容器中要服务。
- 查询服务 → 创建对象及关联对象。
- 对象声明周期 → 从服务容器中那的服务,本身就是一个对象。这个对象你拿过来了,什么时候销毁,这就是指这个对象的生命周期。
- Transient(瞬态):对象拿过来用完了就马上销毁。
- Scoped(范围):拿过来用也会被我这个范围的其它人用,等这个范围用完之后再销毁。
- Singleton(单例):无论谁获取服务,拿到的都是同一个对象。
2.2 .NET中的DI
- 根据类型来获取和注册服务。可以分别指定服务类型(Service Type)和实现类型(Implementation Type)。这两者可能相同,也可能不同。服务类型可以是类,也可以是接口。建议面向接口编程,更灵活。
- .NET控制反转组件取名为DependencyInjection(即依赖注入),但它包含ServiceLocation(即服务定位器)的功能。
using System;
using System.Linq;namespace Demo02
{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! I'm {Name}");}}class Program{static void Main(string[] args){TestServiceImp1 t1 = new TestServiceImp1();t1.Name = "Qinway";t1.SayHi();Console.ReadLine();}}
}控制台输出:
Hi! I'm Qinway说明:如上是常规用法,需要创建接口实现类的对象,然后调用方法。
2.3 .NET中使用DI的步骤
step1 → Install-Package Microsoft.Extensions.DependencyInjection // 安装依赖反转的包
step2 → using Microsoft.Extension.DependencyInjection
step3 → ServiceCollection用来构造容器对象IServiceProvider。// 注册
step4 → 调用ServiceColection的BuildServiceProvider(),用来创建ServiceProvider对象。// 创建
step5 → 通过ServiceProvider对象,可以获取BuildServiceProvider()之前ServiceCollection中的对象。 // 使用
2.4 使用DI示例
- 首先项目中需要安装NuGet包:Install-Package Microsoft.Extensions.DependencyInjection

- 截止2025.09.07日,该包的版本号为:Microsoft.Extensions.DependencyInjection 9.0.8
- 老师课上演示改包的版本号为:Microsoft.Extensions.DependencyInjection 5.0.0
// 如下代码以Microsoft.Extensions.DependencyInjection 9.0.8版本演示
using Microsoft.Extensions.DependencyInjection;
using System;namespace Demo02
{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! I'm {Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection(); // @1 ServiceCollection是一个集合services.AddTransient<TestServiceImp1>(); // @2 添加瞬态服务using (ServiceProvider sp = services.BuildServiceProvider()) // @3 创建ServiceProvider对象{TestServiceImp1 t1 = sp.GetService<TestServiceImp1>(); // @4 t1需要TestServiceImp1对象t1.Name = "Qinway";t1.SayHi();}Console.ReadLine();}}
}说明:
1. 在@1处,创建一个存放服务的集合services。
2. 在@2处,给services集合添加一个瞬态服务TestServiceImp1
3. 在@3处,ServiceProvider实现了IDisposable接口的,要用using()括起来,不然会存在资源泄露的问题。
3.1 等号右边创建了ServiceProvider对象,这个对象相当于ServiceLocation(服务定位器)。
3.2 等号左边创建了一个TestServiceImp1类型变量t1。
4. 在@4处,t1要一个服务,即TestServiceImp1类型的对象
5. 对比本例代码和2.2中的代码。
5.1 在2.2中TestServiceImp1 t1 = new TestServiceImp1();一行代码就搞定的,在本例中搞了8行代码,闲得蛋疼。
5.2 目前来讲看不出来任何意义,后面会继续讲解。
3. 服务的生命周期(视频Part2-26)
3.1 生命周期
- 给类构造函数中打印,看看不同声明周期的对象创建。
- 使用serviceProvider.CreateScope()创建Scope。
- 如果一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法。
- 不要在长生命周期的对象中引用比它短的生命周期的对象。
- 在ASP.NET Core中,这样做默认会抛出异常。
- 生命周期的选择
- 如果类无状态,建议为Singleton。
- 无状态的类:一个类没有属性,没有成员变量。
- 无状态的类没有并发的问题。
- 无状态的类声明为Singleton,可以减少内存占用。
- 如果类有状态,且有Scope控制,建议为Scoped。因为通常这种Scope控制下的代码都是运行在同一个线程中的,没有并发问题;在使用Transient的时候要谨慎,因为每次会创建一个新的对象,占用更多的内存。
- 如果类无状态,建议为Singleton。
- .NET注册服务的重载方法很多,看文档琢磨。
3.2 Transient(瞬态)示例
static void Main(string[] args)
{ServiceCollection services = new ServiceCollection();services.AddTransient<TestServiceImp1>(); // Transient,瞬态using (ServiceProvider sp = services.BuildServiceProvider()){TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();TestServiceImp1 t2 = sp.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t1, t2)); // → False。}
}说明:ReferenceEquals方法用来比较两个变量指向的是否是同一个对象。如果是同一个对象返回True,如果不是同一个对象返回False。注意,如果为瞬态服务,那么每次创建的都是新的对象。
3.3 Singleton(单例)示例
static void Main(string[] args)
{ServiceCollection services = new ServiceCollection();services.AddSingleton<TestServiceImp1>(); // Singleton,单例using (ServiceProvider sp = services.BuildServiceProvider()){TestServiceImp1 t1 = sp.GetService<TestServiceImp1>();TestServiceImp1 t2 = sp.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t1, t2)); // → Truet1.Name = "Qinway";t1.SayHi(); // → Hi! I'm Qinwayt2.Name = "QW"; // 修改t2的属性t1.SayHi(); // → Hi! I'm QW。此时t1指向的对象属性也改变了。}Console.ReadLine();
}注意,如果为单例服务,那么每次都是指向同一个对象。
3.4 Scope(范围)示例
- Scope对象,是由程序员自己设定的。
static void Main(string[] args)
{ServiceCollection services = new ServiceCollection();services.AddScoped<TestServiceImp1>(); // Scoped,范围using (ServiceProvider sp = services.BuildServiceProvider()){TestServiceImp1 tt; // @1 定义一个变量,用来获取第一个Scope范围的对象。using (IServiceScope scope1 = sp.CreateScope()) // using定义了第一个Scope的范围。{// 在scope中获取Scope相关的对象,是scope1.ServiceProvider而不是sp。TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();TestServiceImp1 t2 = scope1.ServiceProvider.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t1, t2)); // → Truett = t1; // @2}using (IServiceScope scope2 = sp.CreateScope()) // using定义了第二个Scope的范围。{TestServiceImp1 t3 = scope2.ServiceProvider.GetService<TestServiceImp1>();TestServiceImp1 t4 = scope2.ServiceProvider.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t3, t4)); // → TrueConsole.WriteLine(object.ReferenceEquals(tt, t3)); // → Fasle @3}}Console.ReadLine();
}说明:在实际开发中在@1处定义的变量,不要在@2处使用内部范围的变量。这里为了演示,后续开发中要避免。
4. IDisposable接口实现示例
- 一个类实现了IDisposable接口,则离开作用域之后容器会自动调用对象的Dispose方法,这样方便内存的管理。
using Microsoft.Extensions.DependencyInjection;
using System;namespace Demo02
{public interface ITestService{public string Name { get; set; }public void SayHi();}public class TestServiceImp1 : ITestService, IDisposable{public string Name { get; set; }public void Dispose(){Console.WriteLine("Dispose...");}public void SayHi(){Console.WriteLine($"Hi! I'm {Name}");}}class Program{static void Main(string[] args){ServiceCollection services = new ServiceCollection();services.AddScoped<TestServiceImp1>();using (ServiceProvider sp = services.BuildServiceProvider()){using (IServiceScope scope1 = sp.CreateScope()){TestServiceImp1 t1 = scope1.ServiceProvider.GetService<TestServiceImp1>();TestServiceImp1 t2 = scope1.ServiceProvider.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t1, t2));} // 离开第一个范围,自动调用Dispose方法using (IServiceScope scope2 = sp.CreateScope()){TestServiceImp1 t3 = scope2.ServiceProvider.GetService<TestServiceImp1>();TestServiceImp1 t4 = scope2.ServiceProvider.GetService<TestServiceImp1>();Console.WriteLine(object.ReferenceEquals(t3, t4));} // 离开第二个范围,自动调用Dispose方法}Console.ReadLine();}}
}控制台输出:
True
Dispose...
True
Dispose...
结尾
书籍:ASP.NET Core技术内幕与项目实战
视频:https://www.bilibili.com/video/BV1pK41137He
著:杨中科
ISBN:978-7-115-58657-5
版次:第1版
发行:人民邮电出版社
※敬请购买正版书籍,侵删请联系85863947@qq.com※
※本文章为看书或查阅资料而总结的笔记,仅供参考,如有错误请留言指正,谢谢!※