依賴注入[7]: .NET Core DI框架[服務注冊]


包含服務注冊信息的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的其他三個屬性體現了服務實例的三種提供方式,並對應着三個構造函數。如果我們指定了服務的實現類型(對應於ImplementationType屬性),那么最終的服務實例將通過調用定義在實現類型中某一個構造函數來創建。如果指定的是一個Func<IServiceProvider, object>對象(對應於ImplementationFactory屬性),那么IServiceProvider對象將會將自身作為輸入參數調用該委托對象來提供服務實例。如果我們直接指定一個現有的對象(對應的屬性為ImplementationInstance),那么該對象就是最終提供的服務實例。

如果我們采用直接提供服務實例的形式來創建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'.”

4-4
圖1實現類型不明確導致的異常

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)));


依賴注入[1]: 控制反轉
依賴注入[2]: 基於IoC的設計模式
依賴注入[3]: 依賴注入模式
依賴注入[4]: 創建一個簡易版的DI框架[上篇]
依賴注入[5]: 創建一個簡易版的DI框架[下篇]
依賴注入[6]: .NET Core DI框架[編程體驗]
依賴注入[7]: .NET Core DI框架[服務注冊]
依賴注入[8]: .NET Core DI框架[服務消費]


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM