依赖注入(Dependency Injection,DI)是控制反转(Inversion of Control,IOC)思想的实现方式。
为什么使用DI?依赖注入简化模块的组装过程,降低模块之间的耦合度
假设有这样的接口和实现类:
public interface ISayHello
{
public void SayHello();
}
public class Chinese : ISayHello, IDisposable
{
public void SayHello()
{
Console.WriteLine("你好!");
}
public void Dispose()
{
Console.WriteLine("资源释放了");
}
}
调用方式:
ISayHello sayHello = new Chinese(); sayHello.SayHello();
基于接口编程自有其好处,但是如上面的方式还是有缺陷的。调用者必须知道是谁实现了ISayHello接口的SayHello方法。这里就需要知道一个接口,一个实现类,加上本身调用的地方,一个简单的调用就有三者耦合。
DI 原理下面让我们通过 DI 的方式实现,首先是 nuget 安装:
Install-Package Microsoft.Extensions.DependencyInjection -Version 6.0.0
上面包是微软实现的实现了依赖注入的包,看看怎么使用(最麻烦的使用方式):
ServiceCollection serviceContainer = new ServiceCollection(); // 构建一个服务容器 serviceContainer.AddTransient(); // 把 Chinese 作为瞬态服务(对象),添加到服务器容器 // 通过容器构建一个 ServiceProvider(服务提供器),ServiceProvider 可以提供容器中管理的服务(已添加到容器的服务) using (ServiceProvider serviceProvider = serviceContainer.BuildServiceProvider()) { ISayHello sayHello = serviceProvider.GetService (); // 从 ServiceProvider 中获取需要的服务(对象) sayHello.SayHello(); }
- 首先创建一个服务(对象)容器
- 把需要服务容器管理的服务(对象)添加到服务容器
- 通过服务容器构建一个 ServiceProvider(服务提供器),ServiceProvider 可以提供容器管理的服务(已添加到容器的服务)
- 通过 ServiceProvider(服务提供器)获取需要的服务(对象)
Singleton(单例)使用一次即销毁,下次使用将是一个新的对象
Scoped(范围)顾名思义,参考单例模式
在一个范围内使用同一个对象
可以通过ServiceProvider的CreateScope方法创建新的范围:
ServiceCollection serviceContainer = new ServiceCollection(); serviceContainer.AddScoped(); using (ServiceProvider serviceProvider = serviceContainer.BuildServiceProvider()) { ISayHello sayHello1 = serviceProvider.GetService (); ISayHello sayHello2 = serviceProvider.GetService (); ISayHello sayHello3 = null; // 应该在 Scope 范围内声明变量 ISayHello sayHello4 = null; // 应该在 Scope 范围内声明变量 ISayHello sayHello5 = null; // 应该在 Scope 范围内声明变量 ISayHello sayHello6 = null; // 应该在 Scope 范围内声明变量 using (IServiceScope serviceScope = serviceProvider.CreateScope()) { sayHello3 = serviceScope.ServiceProvider.GetService (); sayHello4 = serviceScope.ServiceProvider.GetService (); } using (IServiceScope serviceScope = serviceProvider.CreateScope()) { sayHello5 = serviceScope.ServiceProvider.GetService (); sayHello6 = serviceScope.ServiceProvider.GetService (); } Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello2)); // True, 都是通过 serviceProvider 创建 Console.WriteLine(Object.ReferenceEquals(sayHello3, sayHello4)); // True, 都是通过同一个 serviceScope 创建,在单独的一个范围中 Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello4)); // False,在单独的一个范围中,所以和 serviceProvider 创建的不一致 Console.WriteLine(Object.ReferenceEquals(sayHello5, sayHello6)); // True, 同 sayHello3, sayHello4 Console.WriteLine(Object.ReferenceEquals(sayHello1, sayHello6)); // False,同 sayHello1, sayHello4 Console.WriteLine(Object.ReferenceEquals(sayHello4, sayHello6)); // False,在两个 serviceScope 中,所以 False }
为了确保对象正确的生命周期,应该在 Scope 范围内声明变量
由上图可以知道,如果实现类实现了 IDisposable接口,则在离开作用域之后,容器会自动调用对象的Dispose方法。
如何选择生命周期?也印证了需要在范围内声明并使用对象,在外面可能资源被释放了
如果一个类是无状态的,这时可以选择 Singleton。因为无状态的类无所谓并发问题(不会修改状态)。
如果一个类有状态,并且有 Scope 控制,可以注册为 Scoped。因为 Scope 控制的代码都是运行在同一个线程中的,没有并发修改的问题。
最后 Transient 在使用时,需要谨慎,因为每次都会创建一个新的对象。



