Denpendcy Injection 8.0新功能——KeyedService 本文只介紹 .NET Denpendcy Injection 8.0新功能——KeyedService,假定讀者已熟練使用之前版本的功能。 註冊帶Key的類 8.0之前,註冊一個類往往是AddSingleton<IFo ...
Denpendcy Injection 8.0新功能——KeyedService
本文只介紹 .NET Denpendcy Injection 8.0新功能——KeyedService,假定讀者已熟練使用之前版本的功能。
註冊帶Key的類
8.0之前,註冊一個類往往是AddSingleton<IFoo, Foo>()
,8.0添加了一個新功能:“可以註冊一個帶Key的類”AddKeyedSingleton<IFoo, Foo>("keyA")
。獲取服務方法由GetService<IFoo>()
變成了GetKeyedService<IFoo>("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<IFoo>("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>()
,這個“標識”就是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<IFoo>()
就等於調用GetKeyedService<IFoo>(null)
。
再舉一個例子:
//類型的註冊信息放在_descriptorLookup,8.0前,是通過ServiceType作為字典的鍵,
//8.0是把ServiceIdentifier(也就是ServiceKey+ServiceType)作為字典的鍵
//7.0
private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new();
//8.0
private readonly Dictionary<ServiceIdentifier, ServiceDescriptorCacheItem> _descriptorLookup = new();
internal readonly struct ServiceIdentifier : IEquatable<ServiceIdentifier>
{
public object? ServiceKey { get; }
public Type ServiceType { get; }
}
迴圈引用
前面講到可以通過[ServiceKey]
獲取調用時的Key;而沒有註冊key的服務是無法在構造函數中註入key的值。通過這個功能可以解決迴圈引用的問題,先看代碼。
class Foo : IFoo
{
//這個構造函數給GetService<IFoo>()使用
public Foo()
{
this.Num = 10;
}
//這個構造函數給GetKeyedServices<IFoo>("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<IFoo>("keyA")
,是使用的這個構造函數Foo([ServiceKey] string key, IFoo foo)
創建的對象。IFoo foo
是使用無參構造函數創建的。
註意點:
- 參數
[ServiceKey] string key
一定要寫在參數IFoo foo
前面,否則就會迴圈引用 - 註冊服務時,要註冊兩種(帶key的和不帶key的都要註冊)
AddScoped<IFoo, Foo>() .AddKeyedScoped<IFoo, Foo>("keyA")
總結
以前的用法往往是介面對應實現類,通過DI獲取對象,只需要知道介面的名字,就可以通過GetService
方法或者構造函數註入獲取對象。
現在是介面+key對應實現類,通過DI獲取對象,需要知道介面+key。如果key為null就和以前的用法一模一樣。
結束。第一次寫文章如有錯誤,歡迎各位批評指點,謝謝!
本文來自博客園,作者:Turnleft,轉載請註明原文鏈接:https://www.cnblogs.com/tenleft/p/17719609.html