包含服務注冊信息的IServiceCollection對象最終被用來創建作為DI容器的IServiceProvider對象。服務注冊就是創建出現相應的ServiceDescriptor對象並將其添加到指定IServiceCollection集合對象中的過程。
目錄
一、ServiceDescriptor
二、IServiceCollection
Add
Add{Lifetime}
TryAdd
TryAdd{Lifetime}
TryAddEnumerable
RemoveAll & Replace
一、ServiceDescriptor
通過《依賴注入[6]: .NET Core DI編程體驗》的實例演示我們知道作為DI容器的IServiceProvider對象是通過調用IServiceCollection接口的擴展方法BuildServiceProvider創建的,IServiceCollection對象是一個存放服務注冊信息的集合。Cat中的服務注冊是通過一個類型為ServiceRegistry的對象表示的,在IServiceCollection/IServiceProvider為核心的DI框架中,與之對應的類型為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 object 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); }
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); }
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)));
依賴注入[1]: 控制反轉
依賴注入[2]: 基於IoC的設計模式
依賴注入[3]: 依賴注入模式
依賴注入[4]: 創建一個簡易版的DI框架[上篇]
依賴注入[5]: 創建一個簡易版的DI框架[下篇]
依賴注入[6]: .NET Core DI框架[編程體驗]
依賴注入[7]: .NET Core DI框架[服務注冊]
依賴注入[8]: .NET Core DI框架[服務消費]