依賴注入[7]: .NET Core DI框架[服務註冊]
包含服務註冊資訊的IServiceCollection物件最終被用來建立作為DI容器的IServiceProvider物件。服務註冊就是創建出現相應的ServiceDescriptor物件並將其新增到指定IServiceCollection集合物件中的過程。
目錄
一、ServiceDescriptor
二、IServiceCollection
Add
Add{Lifetime}
TryAdd
TryAdd{Lifetime}
TryAddEnumerable
RemoveAll & Replace
一、ServiceDescriptor
通過《
DI框架將服務註冊儲存在一個通過IServiceCollection介面表示的集合之中。如下面的程式碼片段所示,一個IServiceCollection物件本質上就是一個元素型別為ServiceDescriptor的列表。在預設情況下我們使用的是實現該介面的ServiceCollection型別。
public class ServiceDescriptor { public Type ServiceType { get; } public ServiceLifetime Lifetime { get; } public Type ImplementationType { get; } public Func<IServiceProvider, object> ImplementationFactory { get; } public objectServiceDescriptor的其他三個屬性體現了服務例項的三種提供方式,並對應著三個建構函式。如果我們指定了服務的實現型別(對應於ImplementationType屬性),那麼最終的服務例項將通過呼叫定義在實現型別中某一個建構函式來建立。如果指定的是一個Func<IServiceProvider, object>物件(對應於ImplementationFactory屬性),那麼IServiceProvider物件將會將自身作為輸入引數呼叫該委託物件來提供服務例項。如果我們直接指定一個現有的物件(對應的屬性為ImplementationInstance),那麼該物件就是最終提供的服務例項。ImplementationInstance { get; } public ServiceDescriptor(Type serviceType, object instance); public ServiceDescriptor(Type serviceType, Func<IServiceProvider, object> factory, ServiceLifetime lifetime); public ServiceDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime); }
如果我們採用直接提供服務例項的形式來建立ServiceDescriptor物件,意味著服務註冊預設採用Singleton生命週期模式。對於通過其他兩個建構函式建立建立的ServiceDescriptor物件來說,則需要顯式指定採用的生命週期模式。相較於ServiceDescriptor,我們在Cat框架中定義的ServiceRegistry顯得更加精煉,因為我們直接提供了一個型別為Func<Cat,Type[], object>的屬性來提供對應的服務例項。
除了呼叫上面介紹的三個建構函式來建立對應的ServiceDescriptor物件之外,我們還可以提供定義在ServiceDescriptor型別中一系列靜態方法來建立該物件。如下面的程式碼片段所示,ServiceDescriptor提供瞭如下兩個名為Describe的方法過載來建立對應的ServiceDescriptor物件。
public class ServiceDescriptor { public static ServiceDescriptor Describe(Type serviceType, Func<IServiceProvider, object> implementationFactory, ServiceLifetime lifetime); public static ServiceDescriptor Describe(Type serviceType, Type implementationType, ServiceLifetime lifetime); }當我們呼叫上面兩個Describe方法來建立ServiceDescriptor物件的時候總是需要指定採用的生命週期模式,為了讓物件建立變得更加簡單,ServiceDescriptor中還定義了一系列針對三種生命週期模式的靜態工廠方法。如下所示的是針對Singleton模式的一組Singleton方法過載的定義,針對其他兩種模式的Scoped和Transient方法具有類似的定義。
public class ServiceDescriptor { public static ServiceDescriptor Singleton<TService, TImplementation>() where TService: class where TImplementation: class, TService; public static ServiceDescriptor Singleton<TService, TImplementation>(Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService; public static ServiceDescriptor Singleton<TService>(Func<IServiceProvider, TService> implementationFactory) where TService: class; public static ServiceDescriptor Singleton<TService>(TService implementationInstance) where TService: class; public static ServiceDescriptor Singleton(Type serviceType, Func<IServiceProvider, object> implementationFactory); public static ServiceDescriptor Singleton(Type serviceType, object implementationInstance); public static ServiceDescriptor Singleton(Type service, Type implementationType); }
二、IServiceCollection
DI框架將服務註冊儲存在一個通過IServiceCollection介面表示的集合之中。如下面的程式碼片段所示,一個IServiceCollection物件本質上就是一個元素型別為ServiceDescriptor的列表。在預設情況下我們使用的是實現該介面的ServiceCollection型別。
public interface IServiceCollection : IList<ServiceDescriptor> {} public class ServiceCollection : IServiceCollection {}
Add
我們在應用啟動的時候所做的服務註冊就是創建出現相應的ServiceDescriptor物件並將其新增到指定IServiceCollection集合物件中的過程。考慮到服務註冊是一個高頻呼叫的操作,所以DI框架為IServiceCollection介面定義了一系列擴充套件方法完成服務註冊的工作,比如下面的這兩個Add方法可以將指定的一個或者多個ServiceDescriptor物件新增到IServiceCollection集合中。
public static class ServiceCollectionDescriptorExtensions { public static IServiceCollection Add(this IServiceCollection collection, ServiceDescriptor descriptor); public static IServiceCollection Add(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors); }
Add{Lifetime}
DI框架還針對具體生命週期模式為IServiceCollection介面定義了一系列的擴充套件方法,它們會根據提供的輸入創建出對應的ServiceDescriptor物件並將其新增到指定的IServiceCollection物件中。如下所示的是針對Singleton模式的AddSingleton方法過載的定義,針對其他兩個生命週期模式的AddScoped和AddTransient方法具有類似的定義。
public static class ServiceCollectionServiceExtensions { public static IServiceCollection AddSingleton<TService>(this IServiceCollection services) where TService: class; public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services) where TService: class where TImplementation: class, TService; public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, TService implementationInstance) where TService: class; public static IServiceCollection AddSingleton<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory) where TService: class where TImplementation: class, TService; public static IServiceCollection AddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService: class; public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType); public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory); public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, object implementationInstance); public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Type implementationType); }
TryAdd
雖然針對同一個服務型別可以新增多個ServiceDescriptor,但這情況只有在應用需要使用到同一型別的多個服務例項的情況下才有意義,比如我們可以註冊多個ServiceDescriptor來提供同一個主題的多個訂閱者。如果我們總是根據指定的服務型別來提取單一的服務例項,這種情況下一個服務型別只需要一個ServiceDescriptor物件就夠了。對於這種場景我們可能會使用如下兩個名為TryAdd的擴充套件方法,該方法會根據指定ServiceDescriptor提供的服務型別判斷對應的服務註冊是否存在,只有不存在指定型別的服務註冊情況下,我們提供的ServiceDescriptor才會被新增到指定的IServiceCollection物件中。
public static class ServiceCollectionDescriptorExtensions { public static void TryAdd(this IServiceCollection collection, ServiceDescriptor descriptor); public static void TryAdd(this IServiceCollection collection, IEnumerable<ServiceDescriptor> descriptors); }
TryAdd{Lifetime}
擴充套件方法TryAdd同樣具有基於三種生命週期模式的版本,如下所示的針對Singleton模式的TryAddSingleton方法的定義。在指定服務型別對應的ServiceDescriptor不存在的情況下,它們會採用提供的實現型別、服務例項建立工廠以及服務例項來建立生命週期模式為Singleton的ServiceDescriptor物件並將其新增到指定的IServiceCollection物件中。針對其他兩種生命週期模式的TryAddScoped和TryAddTransient方法具有類似的定義。
public static class ServiceCollectionDescriptorExtensions { public static void TryAddSingleton<TService>(this IServiceCollection collection) where TService: class; public static void TryAddSingleton<TService, TImplementation>(this IServiceCollection collection) where TService: class where TImplementation: class, TService; public static void TryAddSingleton(this IServiceCollection collection, Type service); public static void TryAddSingleton<TService>(this IServiceCollection collection, TService instance) where TService: class; public static void TryAddSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory) where TService: class; public static void TryAddSingleton(this IServiceCollection collection, Type service, Func<IServiceProvider, object> implementationFactory); public static void TryAddSingleton(this IServiceCollection collection, Type service, Type implementationType); }
TryAddEnumerable
除了上面介紹的擴充套件方法TryAdd和TryAdd{Lifetime}之外,IServiceCollection介面還具有如下兩個名為TryAddEnumerable的擴充套件方法。當TryAddEnumerable方法在決定將指定的ServiceDescriptor新增到IServiceCollection物件之前,它也會做存在性檢驗。與TryAdd和TryAdd{Lifetime}方法不同的是,該方法在判斷執行的ServiceDescriptor是否存在是會同時考慮服務型別和實現型別。
public static class ServiceCollectionDescriptorExtensions { public static void TryAddEnumerable(this IServiceCollection services, ServiceDescriptor descriptor); public static void TryAddEnumerable(this IServiceCollection services, IEnumerable<ServiceDescriptor> descriptors); }
被TryAddEnumerable方法用來判斷存在性的實現型別不只是ServiceDescriptor的ImplementationType屬性。如果ServiceDescriptor是通過一個指定的服務例項建立的,那麼該例項的型別會作為用來判斷存在與否的實現型別。如果ServiceDescriptor是通過提供的服務例項工廠來建立的,那麼代表服務例項建立工廠的Func<in T, out TResult>物件的第二個引數型別將被用於判斷ServiceDescriptor的存在性。擴張方法TryAddEnumerable的實現邏輯可言通過如下這段程式來驗證。
var services = new ServiceCollection(); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>()); Debug.Assert(services.Count == 1); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>()); Debug.Assert(services.Count == 1); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Foo())); Debug.Assert(services.Count == 1); Func<IServiceProvider, Foo> factory4Foo = _ => new Foo(); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Foo)); Debug.Assert(services.Count == 1); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux, Bar>()); Debug.Assert(services.Count == 2); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(new Baz())); Debug.Assert(services.Count == 3); Func<IServiceProvider, Gux> factory4Gux = _ => new Gux(); services.TryAddEnumerable(ServiceDescriptor.Singleton<IFoobarbazgux>(factory4Gux)); Debug.Assert(services.Count == 4);
如果通過上述策略得到的實現型別為Object,那麼TryAddEnumerable會因為實現型別不明確而丟擲一個ArgumentException型別的異常。這種情況主要發生在提供的ServiceDescriptor物件是由服務例項工廠建立的情況,所以上面例項中用來建立ServiceDescriptor的工廠型別分別為Func<IServiceProvider, Foo>和Func<IServiceProvider, Gux>,而不是Func<IServiceProvider, object>。
var service = ServiceDescriptor.Singleton<IFoobarbazgux>(_ => new Foo()); new ServiceCollection().TryAddEnumerable(service);
假設我們採用如上所示的方式利用一個Lamda表示式來建立一個ServiceDescriptor物件,對於建立的ServiceDescriptor來說,其服務例項工廠是一個Func<IServiceProvider, object>物件,所以當我們將它作為引數呼叫TryAddEnumerable方法的會丟擲如圖1所示的ArgumentException異常,並提示“Implementation type cannot be 'App.IFoobarbazgux' because it is indistinguishable from other services registered for 'App.IFoobarbazgux'.”
RemoveAll & Replace
上面介紹的這些方法最終的目的都是新增新的ServiceDescriptor到指定的IServiceCollection物件中,有的時候我們還希望刪除或者替換現有的某個ServiceDescriptor,這種情況下通常發生在需要對當前使用框架中由某個服務提供的功能進行定製的時候。由於IServiceCollection實現了IList<ServiceDescriptor>介面,所以我們可以呼叫其Clear、Remove和RemoveAt方法來清除或者刪除現有的ServiceDescriptor。除此之外,我們還可以選擇如下這些擴充套件方法。
public static class ServiceCollectionDescriptorExtensions { public static IServiceCollection RemoveAll<T>( this IServiceCollection collection); public static IServiceCollection RemoveAll(this IServiceCollection collection, Type serviceType); public static IServiceCollection Replace(this IServiceCollection collection, ServiceDescriptor descriptor); }RemoveAll和RemoveAll<T>方法幫助我們針對指定的服務型別來刪除新增的ServiceDescriptor。Replace方法會使用指定的ServiceDescriptor去替換第一個具有相同服務型別(對應ServiceType屬性)的ServiceDescriptor,實際操作是先刪除後新增。如果從目前的IServiceCollection中找不到服務型別匹配的ServiceDescriptor,指定的ServiceDescriptor會直接新增到IServiceCollection物件中,這一邏輯也可以利用如下的程式來驗證。
var services = new ServiceCollection(); services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Foo>()); Debug.Assert(services.Any(it => it.ImplementationType == typeof(Foo))); services.AddSingleton<IFoobarbazgux, Bar>(); services.Replace(ServiceDescriptor.Singleton<IFoobarbazgux, Baz>()); Debug.Assert(!services.Any(it=>it.ImplementationType == typeof(Foo))); Debug.Assert(services.Any(it => it.ImplementationType == typeof(Bar))); Debug.Assert(services.Any(it => it.ImplementationType == typeof(Baz)));