Denpendcy Injection 8.0新功能——KeyedService
本文只介绍 .NET Denpendcy Injection 8.0新功能——KeyedService,假定读者已熟练使用之前版本的功能。
注册带Key的类
8.0之前,注册一个类往往是Add服务器托管网Singleton()
,8.0添加了一个新功能:“可以注册一个带Key的类”AddKeyedSingleton("keyA")
。获取服务方法由GetService()
变成了GetKeyedService("keyA")
,并且调用这两个方法创建出来的对象是不同的。
如果想通过构造函数注入,只需要在参数前面加上特性[FromKeyedServices("keyA")]
即可,特性里的参数就是key的名字。如果想在构造函数中获取key的值则使用特性[ServiceKey]
。我们还可以注册时把key设置为KeyedService.AnyKey
(这是框架提供的类),只需使用任意非null值作为key就可以获取对象。暂时不支持使用通配符匹配,也许以后会加……
class Bar : IBar
{
public Bar([ServiceKey] int key, [FromKeyedServices("keyA")] IFoo foo, IServiceProvider root)
{
//注意:key的类型要和调用时一致。
Console.WriteLine($"key:{key},Compare:{foo == root.GetKeyedService("keyA")}");
}
}
public static class KeyedService
{
/// Represents a key that matches any key.
public static object AnyKey { get; } = new AnyKeyObj();
服务器托管网 private sealed class AnyKeyObj
{
public override string? ToString() => "*";
}
}
深入理解
8.0之前,获取一个对象需要用到的一个“标识”,比如调用GetService()
,这个“标识”就是IFoo
;也就是ServiceDescriptor
里面的ServiceType
。而在8.0后“标识”变成了IFoo+"keyA"
,也就是ServiceDescriptor
里面的ServiceType
+新增的ServiceKey
。
public class ServiceDescriptor
{
public object? ServiceKey { get; }
public Type? ImplementationType => _implementationType;
public Type ServiceType { get; }
public ServiceLifetime Lifetime { get; }
对于以前注册的类,ServiceKey
默认是null
,所以“标识”就是ServiceKey+null
。调用GetService()
就等于调用GetKeyedService(null)
。
再举一个例子:
//类型的注册信息放在_descriptorLookup,8.0前,是通过ServiceType作为字典的键,
//8.0是把ServiceIdentifier(也就是ServiceKey+ServiceType)作为字典的键
//7.0
private readonly Dictionary _descriptorLookup = new();
//8.0
private readonly Dictionary _descriptorLookup = new();
internal readonly struct ServiceIdentifier : IEquatable
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
}
循环引用
前面讲到可以通过[ServiceKey]
获取调用时的Key;而没有注册key的服务是无法在构造函数中注入key的值。通过这个功能可以解决循环引用的问题,先看代码。
class Foo : IFoo
{
//这个构造函数给GetService()使用
public Foo()
{
this.Num = 10;
}
//这个构造函数给GetKeyedServices("keyA")使用
public Foo([ServiceKey] string key, IFoo foo)
{
Console.WriteLine($"key:{key},this.Num:{this.Num},foo.Num:{foo.Num}");
}
public int Num { get; set; }
}
代码执行流程:
- 1.DI首先获取Foo的所有构造函数并且按构造函数的参数从多到少进行排序
- 2.遍历所有构造函数,首先获取参数最多的构造函数
Foo([ServiceKey] string key, IFoo foo)
,开始判断构造函数的参数能否被DI创建 - 3.DI首先判断
string key
这个参数,能够创建;然后继续判断第二个参数IFoo foo
能否被创建 - 4.重复第一步
- 5.重复第二步
- 6.DI首先判断
string key
这个参数,不能够创建;所以无法调用构造函数Foo([ServiceKey] string key, IFoo foo)
创建Foo实例 - 7.继续遍历构造函数,第二个构造函数是无参的,DI能够创建
foo
对象。 - 8.对于
GetKeyedServices("keyA")
,是使用的这个构造函数Foo([ServiceKey] string key, IFoo foo)
创建的对象。IFoo foo
是使用无参构造函数创建的。
注意点:
- 参数
[ServiceKey] string key
一定要写在参数IFoo foo
前面,否则就会循环引用 - 注册服务时,要注册两种(带key的和不带key的都要注册)
AddScoped() .AddKeyedScoped("keyA")
总结
以前的用法往往是接口对应实现类,通过DI获取对象,只需要知道接口的名字,就可以通过GetService
方法或者构造函数注入获取对象。
现在是接口+key对应实现类,通过DI获取对象,需要知道接口+key。如果key为null就和以前的用法一模一样。
结束。第一次写文章如有错误,欢迎各位批评指点,谢谢!
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: Verilog 顺序块、并行块,阻塞过程赋值、非阻塞过程赋值的区别
顺序块:顺序块中的语句执行都是从上到下顺序执行,和C语言执行过程一样,关键词是begin、end。 initial begin//顺序块关键字 tb_sel = 1’b0; #5 tb_in1 = 1’b1;//时刻5执行 #10 tb_in2 = 1’b1;…