基本使用
1.安装
使用nuget 搜索并安装最新版本,包含以下命名空间:
- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.DependencyInjection.Extensions
2.注册服务
//预定义类型
{
public interface IInterfaceA {}
public interface IInterfaceB {}
public interface IInterfaceC {}
public class ClassA:IInterfaceA {}
public class ClassAA:IInterfaceA {}
public class ClassB:IInterfaceB {}
public class ClassC:IInterfaceC {}
public class ClassD
{
//只支持通过构造函数的依赖注入,不支持属性(方法)注入
public ClassD(IInterfaceA a,IInterfaceB b,IInterfaceC c){}
}
}
//注册服务
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IInterfaceA ,ClassA>();
serviceCollection.AddTransient<IInterfaceA ,ClassAA>();
serviceCollection.AddTransient<IInterfaceB ,ClassB>();
serviceCollection.AddTransient<IInterfaceC ,ClassC>();
serviceCollection.AddTransient<ClassD>();
var provider = serviceCollection.BuildServiceProvider();
}
注册顺序在大多数情况下不需要考虑依赖层级关系,比如A依赖于B,B依赖于C,这时你可以任意顺序注册这个三个服务。(但对于相同接口的不同实现总是使用最后一个实现)
3.获取服务
ServiceProvider provider;
//获取所有注册实现IInterfaceA的服务
{
var services = provider.GetServices<IInterfaceA>();
}
//获取最后一个注册的IInterfaceA服务
{
var service = provider.GetService<IInterfaceA>();
}
//获取ClassD,其依赖的IInterfaceA、IInterfaceB、IInterfaceC将由provider解析
{
var d = provider.GetService<ClassD>();
}
4.注册和解析Keyed服务
当我们的接口有多个实现时(像之前的IInterfaceA接口),有时我们想要获取特定的实现时,可以使用含有Keyed的方法进行注册和解析。(keyed类型方法使用方式之一),具体使用方式:
//使用Keyed进行注册
{
serviceCollection.AddKeyedTransient<IInterfaceA ,ClassA>("A");
serviceCollection.AddKeyedTransient<IInterfaceA ,ClassAA>("AA");
//若serviceKey为null,则AddKeyedTransient等同于AddTransient
serviceCollection.AddKeyedTransient<IInterfaceA ,ClassAA>(null);
//serviceKey可以重复,可以通过GetKeyedServices一次全部获取相同serviceKey的服务
serviceCollection.AddKeyedTransient<IInterfaceB ,ClassB>("NotInterfaceA");
serviceCollection.AddKeyedTransient<IInterfaceC ,ClassC>("NotInterfaceA");
}
//使用Keyed方法解析
{
//获取ClassA实现
provider.GetKeyedService<IInterfaceA>("A");
//获取ClassAA实现
provider.GetKeyedService<IInterfaceA>("AA");
}
其它使用方式(我能想到的)
- 对服务进行归类,比如定义一个枚举......
- 定义别名用于快速检索
5.使用 ActivatorUtilities 注册和解析服务
使用情景:
- 用于创建未在容器中创建的服务
- 创建不易被容器解析的类型,比如string等
public class ClassU
{
//IInterfaceA 和 IInterfaceB 已注册
public ClassU(IInterfaceA a,IInterfaceB b){}
}
//使用GetServiceOrCreateInstance方法
{
//1.能够创建通过容器中已注册的服务进行实例化的对象(上面的ClassU)
//provider 负责提供ClassU的依赖
var u = ActivatorUtilities.GetServiceOrCreateInstance<ClassU>(provider);
//2.解析已在容器中注册的服务(非Keyed)
var a = ActivatorUtilities.GetServiceOrCreateInstance<IInterfaceA>(provider);
}
public class ClassUa
{
//IInterfaceA 和 IInterfaceB 已注册
public ClassU(IInterfaceA a,IInterfaceB b,string str){}
}
//使用CreateInstance方法
{
//能够既依赖容器中注册的服务以及其它自定义参数进行实例化的对象(上面的ClassUa)
var ua =ActivatorUtilities.CreateInstance<ClassUa>(provider,"str1");
var ua1 =ActivatorUtilities.CreateInstance<ClassUa>(provider,"str2");
}
6.使用lifetime为作用域来注册解析服务
服务具有生命周期,生命周期的描述见下一节
public class ClassUb:IDisposable
{
//IInterfaceA 和 IInterfaceB 已注册(瞬态)
IInterfaceA a;
IInterfaceB b;
public ClassUb(IServiceProvider provider)
{
a=provider.GetService<IInterfaceA>();
b=provider.GetService<IInterfaceB>();
}
public void Dispose()
{
Console.WriteLine("ClassUb Disposable");
}
}
//ClassUb的注册 (这边使用的是 AddScoped 方法,且BuildServiceProvider的validateScopes=true)
var rootProvider = serviceCollection.AddScoped<ClassUb>().BuildServiceProvider(true);
//解析
using(var scope = rootProvider.CreateScope()) //创建了作用域
{
//使用当前作用域中的容器解析ClassUb
var provider = scope.ServiceProvider;
var ub = provider.GetService<ClassUb>();
}
//释放scope时也会将作用域中的ClassUb释放掉
服务的生命周期
1.描述
其由 Microsoft.Extensions.DependencyInjection.ServiceLifetime 枚举定义:
- Singleton [单例]
- Scoped [作用域]
- Transient [瞬态]
2.瞬态
之前的例子中都是使用瞬态的生命周期添加服务的, 它表现为每次从容器中解析都创建一个新的实例。
3.单例
像设计模式中的单例模式一样,它表现为每次从容器中解析都返回同一个实例,要注意其线程安全及内存占用等问题。
4.作用域
作用域可以定义在一个方法中或者一个类中或其它形式,其表现为在同一作用域中每次解析相同类型返回同一个实例,在不同作用域中解析为不同实例。(在相同作用域中表现为单例,不同作用域中表现为瞬态)
5.在根作用域下解析生命周期为作用域的服务
根作用域的生命周期和应用一致,所以当在根作用域下解析生命周期为作用域的服务的生命周期将自动升级为单例。
如果要验证这种错误的话,需要在调用BuildServiceProvider方法时设定validateScopes = true,其表现为在根作用域下解析此类服务时会抛出异常。
服务的释放
1.原理
当实现IDisposable或IAsyncDisposable的服务被注册到容器中并解析时,ServiceProviderEngineScope会把其整理为一个List,用于在其容器或者作用域释放时,使用这个List进行服务的释放(以添加逆序进行释放)。不满足以上要求的服务将会通过GC进行释放。
2.注意点
1.在根作用域中解析的实现了IDisposable接口的瞬态实例,其释放将会被延迟到根作用域释放的时候(应用关闭等),这不利于实例的释放。
2.在子作用域中也要注意瞬态和单例实例的释放
3.当实例实现了IDisposable接口,其就应该由解析其的作用域进行释放