前段時間看了蔣老師的Core文章,對於DI那一塊感覺挺有意思,然后就看了一下Core官方DI的源碼,這也算是第一個看得懂大部分源碼的框架,雖然官方DI相對來說特別簡單,
官方DI相對於其它框架(例如 autofac)使用起來麻煩許多,既沒有一次注入程序集中所有類的功能,也沒有方便的屬性注入,所以感覺起來官方的DI框架只是一個簡單的標准,
🔔屬性注入:一種被稱為
service Locator
的模式,蔣老師在Core文章中也推薦了建議不要使用這種模式
首先從`ServiceDescriptor`和`ServiceCollection`來認識,這兩個類也是注冊時使用的類
ServiceDescriptor,ServiceCollection
這兩個類是我們使用注冊服務的兩個類型,注冊服務時,DI都會封裝成一個`ServiceDescriptor`類型進行緩存到`ServiceCollection`類型中,其中`ServiceCollection`有三個擴展類型
ServiceCollectionServiceExtensions : 實現了各種我們所使用了注冊方式
**ServiceCollectionDescriptorExtensions ** 實現了各種TryAdd和刪除替換等操作
ServiceCollectionContainerBuilderExtensions 實現了構造
ServiceProvider
實例
ServiceCollection
使用官方DI時注冊我們都是將服務注冊到一個`ServiceCollection`對象中,`ServiceCollection`類型看名稱感覺就是一個服務集合的類型,其實並沒有錯,`IServiceCollection`集合就是一個繼承`IList<ServiceDescriptor>`集合接口的一個類型,而`ServiceDescriptor`類型則是一個注冊的服務描述類型,我們傳入注冊最后都會封裝為一個`ServiceDescriptor`類型然后緩存到`ServiceCollection`集合之中
調用ServiceCollection實例對象的方法進行注冊
static void Main(string[] args)
{
// 使用ServiceCollaction對象的擴展方法進行注冊服務
IServiceCollection services = new ServiceCollection()
// 提供具體實例類
.AddScoped<IFoo, Foo>()
// 提供實例化具體的工廠
.AddScoped(typeof(IBar), _ => new Bar())
// 提供具體實例化對象,此方法只適用於Singleton生命周期
.AddSingleton(typeof(IBaz),new Baz());
}
**IServiceCollection類型的繼承關系**
/// <summary>
/// Specifies the contract for a collection of service descriptors.
/// </summary>
public interface IServiceCollection : IList<ServiceDescriptor>{}
`ServiceCollection`本身類型中只有一些IList<T>具體實現方法,而所有注冊的方法都是以擴展方法提供在一個 `ServiceCollectionServiceExtensions` `ServiceCollectionDescriptorExtensions`這兩個擴展類中
🔔
ServiceCollectionDescriptorExtensions
擴展類中大多都是TryAdd
添加(不存在則添加),添加時參數直接為ServiceDescriptor對象或者有刪除或替換操作🔔
ServiceCollectionServiceExtensions
擴展類則以上面例子那樣進行傳入基類與派生類類型(派生類對象或工廠)
**ServiceCollection類型可用成員**
/// <summary>
/// Default implementation of <see cref="IServiceCollection"/>.
/// </summary>
public class ServiceCollection : IServiceCollection
{
// ServiceDescriptor緩存集合,ServiceDescriptor對象緩存到這個屬性中
private readonly List<ServiceDescriptor> _descriptors = new List<ServiceDescriptor>();
// 注冊到當前ServiceCollection對象中的ServiceDescriptor數量
public int Count => _descriptors.Count;
public bool IsReadOnly => false;
// 設置索引器
public ServiceDescriptor this[int index]
{
get=> _descriptors[index];
set=> _descriptors[index] = value;
}
// 清空所有注冊到此ServiceCollection上的ServiceDescriptor對象
public void Clear() => _descriptors.Clear();
// 查詢此ServiceCollection是否包含指定ServiceDescriptor對象
public bool Contains(ServiceDescriptor item)=> _descriptors.Contains(item);
// 拷貝ServiceDescriptor
public void CopyTo(ServiceDescriptor[] array, int arrayIndex) =>_descriptors.CopyTo(array, arrayIndex);
// 從此ServiceCollection移除指定ServiceDescriptor
public bool Remove(ServiceDescriptor item)=>_descriptors.Remove(item);
// 獲取此ServiceCollection的迭代器
public IEnumerator<ServiceDescriptor> GetEnumerator()=> _descriptors.GetEnumerator();
public int IndexOf(ServiceDescriptor item) => _descriptors.IndexOf(item);
public void Insert(int index, ServiceDescriptor item) => _descriptors.Insert(index, item);
public void RemoveAt(int index)=> _descriptors.RemoveAt(index);
}
ServiceCollectionServiceExtensions
在大部分我們都是調用`ServiceCollectionServiceExtensions`擴展類的方法進行注冊到Collection之中的,在這個擴展中提供了大量的重載,以便允許我們采用不同的方式進行注冊,*泛型* *類型參數* 等
// 列出Sinleton生命周期一部分,Scoped和Transient生命周期都一致
// 基類型和派生類型
public static IServiceCollection AddSingleton(this IServiceCollection services,Type serviceType,Type implementationType);
// 基類型和派生類型工廠
public static IServiceCollection AddSingleton(this IServiceCollection services,Type serviceType,Func<IServiceProvider, object> implementationFactory)
// 基類型和派生類型泛型
public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services)
where TService : class where TImplementation : class, TService
// 此方法注冊 services必須是一個實例化的類型
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services)
where TService : class
// 基類型與派生類型實例對象,此方式適用於Sinleton生命周期
public static IServiceCollection AddSingleton<TService>(this IServiceCollection services,TService implementationInstance)
where TService : class
雖然在`ServiceCollectionServiceExtensions`擴展類中具有大量的重載,但是這是重載都是一些"虛"方法,其最終只是使用了3個方法進行注冊
// 使用基類和派生類類型實例化ServiceDescriptor對象,然后進行緩存,
private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Type implementationType,ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationType, lifetime);
collection.Add(descriptor);
return collection;
}
// 使用基類型和工廠實例化ServiceDescriptor對象,然后進行緩存
private static IServiceCollection Add(IServiceCollection collection,Type serviceType,Func<IServiceProvider, object> implementationFactory,ServiceLifetime lifetime)
{
var descriptor = new ServiceDescriptor(serviceType, implementationFactory, lifetime);
collection.Add(descriptor);
return collection;
}
// 使用基類型和具體實例對象實例化ServiceDescriptor對象,然后進行緩存
// 此方法只適用於Singleton生命周期
public static IServiceCollection AddSingleton(this IServiceCollection services,Type serviceType,object implementationInstance)
{
var serviceDescriptor = new ServiceDescriptor(serviceType, implementationInstance);
services.Add(serviceDescriptor);
return services;
}
所調用的注冊服務方式最后都是調用上面三個方法進行注冊,微軟只是只是提供了大量的殼子,從上面可以看出`ServiceDescriptor`類具有三個構造器起碼,分別以三種方式進行實例化
ServiceCollectionDescriptorExtensions
`ServiceCollectionDescriptorExtensions`擴展類中具有 `Replace` `RemoveAll` `Add`(參數為`ServiceDescriptor`)和很多重載的`TryAdd`方法
Replace(替換方法) 由新的ServiceDescriptor對象替換ServiceType的第一個ServiceDescriptor對象
// 使用一個新的ServiceDescriptor對象替換指定基類的第一個ServiceDescriptor
public static IServiceCollection Replace(this IServiceCollection collection,ServiceDescriptor descriptor)
{
// 獲取注冊的第一個serviceType進行刪除並添加進這個新的ServiceDescriptor
var registeredServiceDescriptor = collection.FirstOrDefault(s => s.ServiceType == descriptor.ServiceType);
if (registeredServiceDescriptor != null)
collection.Remove(registeredServiceDescriptor);
collection.Add(descriptor);
return collection;
}
RemoveAll(刪除方法) 從Collection刪除指定ServiceType的所有ServiceDescriptor對象
// 移除指定ServiceType的所有ServiceDescriptor
public static IServiceCollection RemoveAll(this IServiceCollection collection, Type serviceType)
{
for (var i = collection.Count - 1; i >= 0; i--)
{
var descriptor = collection[i];
if (descriptor.ServiceType == serviceType)
collection.RemoveAt(i);
}
return collection;
}
// 移除指定泛型類型的所有ServiceDescriptor
public static IServiceCollection RemoveAll<T>(this IServiceCollection collection) => RemoveAll(collection, typeof(T));
Add(添加方法) 參數直接為ServiceDescriptor對象
public static IServiceCollection Add(this IServiceCollection collection,ServiceDescriptor descriptor)
{
collection.Add(descriptor);
return collection;
}
public static IServiceCollection Add(this IServiceCollection collection,IEnumerable<ServiceDescriptor> descriptors)
{
foreach (var descriptor in descriptors)
collection.Add(descriptor);
return collection;
}
TryAdd和TryAddEnumerable方法
TryAdd和TryAddEnumerable這兩個方法是如果不存在則添加,其中TryAdd
方法具有大量的包裝方法,跟ServiceCollectionServiceExtensions
中Add
方法差不多,
TryAdd方法如果當前ServiceType
已被注冊,那么再次注冊就不會成功
public static void TryAdd(this IServiceCollection collection,ServiceDescriptor descriptor)
{
if (!collection.Any(d => d.ServiceType == descriptor.ServiceType))
collection.Add(descriptor);
}
有許多類似TryAddTransient
方法進行了包裝TryAdd
public static void TryAddTransient(this IServiceCollection collection,Type service)
{
// 使用ServiceDescriptor的靜態方法創建實例化方法,
// 此靜態方法用於實例一個ServiceDescriptor對象,也是擁有大量重載
var descriptor = ServiceDescriptor.Transient(service, service);
TryAdd(collection, descriptor);
}
TryAddEnumerable方法在添加時除了判斷基類型之外也會判斷其派生類型是否被注冊過
public static void TryAddEnumerable(this IServiceCollection services,ServiceDescriptor descriptor)
{
// ServiceDescriptor.GetImplementationType()是獲取派生類型
// 使用TryAddEnumerable進行判斷時也會判斷其派生類型
var implementationType = descriptor.GetImplementationType();
if (implementationType == typeof(object) ||
implementationType == descriptor.ServiceType)
{
throw new ArgumentException(
Resources.FormatTryAddIndistinguishableTypeToEnumerable(
implementationType,
descriptor.ServiceType),
nameof(descriptor));
}
// 如果當前基類型和當前派生類型沒有注冊過,便進行注冊
if (!services.Any(d =>
d.ServiceType == descriptor.ServiceType &&
d.GetImplementationType() == implementationType))
services.Add(descriptor);
}
public static void TryAddEnumerable(this IServiceCollection services,IEnumerable<ServiceDescriptor> descriptors)
{
foreach (var d in descriptors)
services.TryAddEnumerable(d);
}
ServiceCollectionContainerBuilderExtensions
這個擴展類是創建`IServiceProvider`的,在這個擴展類中只具有`BuildServiceProvider()`方法,這個方法也就是我們用來獲取`ServiceProvider`類型,`ServiceProvider`是獲取服務對象的類型
public static ServiceProvider BuildServiceProvider(this IServiceCollection services)
// 使用默認的ServiceProviderOptions實例
=>BuildServiceProvider(services, ServiceProviderOptions.Default);
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes)
=>services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes });
public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options)
=> new ServiceProvider(services, options);
可以看到這個方法具有三個重載,在所有重載中都有一個`ServiceProviderOptions`類型,這是一個什么類型呢, 首先看一下這個類型定義
ServiceProviderOptions
public class ServiceProviderOptions
{
internal static readonly ServiceProviderOptions Default = new ServiceProviderOptions();
/// <summary>
/// 當此屬性為true,不能從獲取頂級容器中的scoped
/// </summary>
public bool ValidateScopes { get; set; }
/// <summary>
/// 實例化ServiceProvider模式,當前只能使用Dynamic模式
/// </summary>
internal ServiceProviderMode Mode { get; set; } = ServiceProviderMode.Dynamic;
}
這個類中具有三個數據,一個是當前類的默認實例`Default` ,一個是實例化`ServiceProvider`的模式 `ServiceProvderMode`是一個枚舉,默認為`Dynamic`,這個屬性是`internal`修飾的,所以在外部使用時是不可以設置的,然而目前這三種都是使用了`Dynamic`
internal enum ServiceProviderMode
{
Dynamic,
Runtime,
Expressions,
ILEmit
}
還有一個Bool類型屬性`ValidateScopes`,如果這個類型為true,則不能從頂級容器中獲取scoped生命周期的服務
ServiceDescriptor
此類型是服務注冊的描述類型,此類型中擁有注冊的`ServiceType(基類型)` `ImplementationType(派生類型)/具體服務對象/實例化服務類型的工廠` 和注冊服務的生命周期`Lifetime`
// 注冊的類型的生命周期
/// <inheritdoc />
public ServiceLifetime Lifetime { get; }
// 注冊類型的基類型
/// <inheritdoc />
public Type ServiceType { get; }
// 注冊類型的實例類型(派生類型)
/// <inheritdoc />
public Type ImplementationType { get; }
// 注冊類型的實例對象
/// <inheritdoc />
public object ImplementationInstance { get; }
// 注冊類型實例化對象的工廠
/// <inheritdoc />
public Func<IServiceProvider, object> ImplementationFactory { get; }
ServiceDescriptor
類型中具有三個構造函數,就是使用派生類型,工廠和具體實例對象三種實例化服務對象方式
public ServiceDescriptor(Type serviceType,object instance)
: this(serviceType, ServiceLifetime.Singleton)
{
Lifetime = lifetime;
ServiceType = serviceType;
// 對內部維護的注冊類型對象進行賦值
ImplementationInstance = instance;
}
public ServiceDescriptor(Type serviceType,Func<IServiceProvider, object> factory,ServiceLifetime lifetime)
: this(serviceType, lifetime)
{
Lifetime = lifetime;
ServiceType = serviceType;
// 對內部維護的實例化注冊對象的工廠進行賦值
ImplementationFactory = factory;
}
public ServiceDescriptor(Type serviceType,Type implementationType,ServiceLifetime lifetime)
: this(serviceType, lifetime)
{
Lifetime = lifetime;
ServiceType = serviceType;
// 對象內部維護的實現類型進行賦值
ImplementationType = implementationType;
}
此類中方法具有一個獲取實際注冊類型`GetImplementationType()`和一批實例化`ServiceDescriptor`對象的方法 `GetImplementationType()`方法根據其實例化`ServiceDescriptor`的方法進行判斷獲取實例化的實際類型,
🔔 訪問修飾符是internal,所以此方法並沒有對外開放,只允許內部使用
/// <summary>
/// 獲取當前注冊類型的實例類型
/// </summary>
/// <returns></returns>
internal Type GetImplementationType()
{
if (ImplementationType != null)
return ImplementationType;
else if (ImplementationInstance != null)
return ImplementationInstance.GetType();
else if (ImplementationFactory != null)
{
var typeArguments = ImplementationFactory.GetType().GenericTypeArguments;
return typeArguments[1];
}
return null;
}
實例化本類對象的方法具有很多重載,跟`ServiceCollectionDescriptorExtensions``ServiceCollectionServiceExtensions`擴展類一樣,其中`ServiceCollectionDescriptorExtensions`擴展類中便利用了這些方法進行實例化此對象
// 真正實例化對象的方法,重載都是調用此類方法
public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime)
=> new ServiceDescriptor(serviceType, implementationFactory, lifetime);
public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime)
=> new ServiceDescriptor(serviceType, implementationType, lifetime);
// 此方法只有Sinleton生命周期才能調用
public static ServiceDescriptor Singleton(Type serviceType,object implementationInstance)
=>new ServiceDescriptor(serviceType, implementationInstance);
測試
TryAdd
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection()
.AddScoped(typeof(IBaz),typeof(Baz2));
// 嘗試注冊使用TryAdd再次注冊IBaz類型
services.TryAdd(new ServiceDescriptor(typeof(IBaz), typeof(Baz1), ServiceLifetime.Scoped));
var provider= services.BuildServiceProvider();
// 獲取所有IBaz的注冊對象
IList<IBaz> baz= provider.GetServices<IBaz>().ToList();
Console.WriteLine("獲取到的數量:"+baz.Count);
// 循環輸出所有實際對象類型
foreach (var item in baz)
Console.WriteLine("實際類型:" + item.GetType());
}
從結果看出TryAdd
方法並沒有將IBaz
再次注冊到ServiceCollection
對象
TryAddEnumerable
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection()
.AddScoped(typeof(IBaz),typeof(Baz2));
// 使用TryAddEnumerable嘗試注冊
services.TryAddEnumerable(new ServiceDescriptor(typeof(IBaz), typeof(Baz2), ServiceLifetime.Scoped));
services.TryAddEnumerable(new ServiceDescriptor(typeof(IBaz), typeof(Baz1), ServiceLifetime.Scoped));
var provider= services.BuildServiceProvider();
IList<IBaz> baz= provider.GetServices<IBaz>().ToList();
Console.WriteLine("獲取到的數量:"+baz.Count);
foreach (var item in baz)
Console.WriteLine("實際類型:" + item.GetType());
}
🔔注意:使用TryAddEnumerable進行注冊時不能使用工廠方法實例對象那種方式
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection()
.AddScoped(typeof(IBaz),typeof(Baz2));
// 使用工廠方法實例化對象方式
var service = ServiceDescriptor
.Scoped<IBaz>(_ => new Baz1());
// 使用TryAddEnumerable進行注冊,會拋出一個System.ArgumentException異常
services.TryAddEnumerable(service);
}
測試ServiceProviderOptions的ValidateScopes
static void Main(string[] args)
{
// 頂級容器
IServiceProvider provider = new ServiceCollection()
.AddScoped(typeof(IFoo), typeof(Foo))
// 設置不能從頂級容器中獲取scoped生命周期服務
.BuildServiceProvider(true);
// 頂級容器構造Foo對象
var foo1= provider.GetService<IFoo>();
}
如果運行上面程序,則會拋出一個InvalidOperationException`異常
可以看到並不允許讓我們創建頂級容器的scoped服務對象,但是如果我們使用子容器就不會拋出異常
static void Main(string[] args)
{
// 頂級容器
IServiceProvider provider = new ServiceCollection()
.AddScoped(typeof(IFoo), typeof(Foo))
// 設置不能從頂級容器中獲取scoped生命周期服務
.BuildServiceProvider(true);
// 子容器
IServiceProvider childProvider= provider.CreateScope().ServiceProvider;
var foo2= childProvider.GetService<IFoo>();
}