1. 程式人生 > >[ASP.NET Core 3框架揭祕] 依賴注入:一個Mini版的依賴注入框架

[ASP.NET Core 3框架揭祕] 依賴注入:一個Mini版的依賴注入框架

在前面的章節中,我們從純理論的角度對依賴注入進行了深入論述,我們接下來會對.NET Core依賴注入框架進行單獨介紹。為了讓讀者朋友能夠更好地理解.NET Core依賴注入框架的設計與實現,我們按照類似的原理建立了一個簡易版本的依賴注入框架,也就是我們在前面多次提及的Cat。

原始碼下載

普通服務的註冊與消費
泛型服務的註冊與消費
多服務例項的提供
服務例項的生命週期

一、程式設計體驗

雖然我們對這個名為Cat的依賴注入框架進行了最大限度的簡化,但是與.NET Core框架內部使用的真實依賴注入框架相比,Cat不僅採用了一致的設計,而且幾乎具備了後者所有的功能特性。為了讓大家對Cat具有一個感官的認識,我們先來演示一下如何利用它來提供我們所需的服務例項。

作為依賴注入容器的Cat物件不僅僅作為服務例項的提供者,它同時還需要維護著服務例項的生命週期。Cat提供了三種生命週期模式,如果要了解它們之間的差異,就必須對多個Cat之間的層次關係有充分的認識。一個代表依賴注入容器的Cat物件用來建立其他的Cat物件,後者視前者為“父容器”,所以多個Cat物件通過其“父子關係”維繫一個樹形層次化結構。不過這僅僅是一個邏輯結構而已,實際上每個Cat物件只會按照下圖所示的方式引用整棵樹的根。

在瞭解了多個Cat物件之間的關係之後,對於三種預定義的生命週期模式就很好理解了。如下所示的Lifetime列舉代表著三種生命週期模式,其中Transient代表容器針對每次服務請求都會建立一個新的服務例項,而Self則是將提供服務例項儲存在當前容器中,它代表針對某個容器範圍內的單例模式,Root則是將每個容器提供的服務例項統一存放到根容器中,所以該模式能夠在多個“同根”容器範圍內確保提供的服務是單例的。

public enum Lifetime
{
    Root,
    Self,
    Transient
}

代表依賴注入容器的Cat物件之所以能夠為我們提供所需服務例項,其根本前提是相應的服務註冊在此之前已經新增到容器之中。服務總是針對服務型別(介面、抽象類或者具體型別)進行註冊,Cat通過定義的擴充套件方法提供瞭如下三種註冊方式。除了直接提供服務例項的形式外(預設採用Root模式),我們在註冊服務的時候必須指定一個具體的生命週期模式。

  • 指定具體的實現型別。

  • 提供一個服務例項。

  • 指定一個建立服務例項的工廠。

我們定義瞭如下的介面和對應的實現型別來演示針對Cat的服務註冊。其中Foo、Bar、Baz和Gux分別實現了對應的介面IFoo、IBar、IBaz和IGux,其中Gux型別上標註了一個MapToAttribute特性註冊了與對應介面IGux之間的對映。為了反映Cat對服務例項生命週期的控制,我們讓它們派生於同一個基類Base。Base實現了IDisposable介面,我們在其建構函式和實現的Dispose方法中輸出相應的文字以確定對應的例項何時被建立和釋放。我們還定義了一個泛型的介面IFoobar<T1, T2>和對應的實現類Foobar<T1, T2>來演示Cat針對泛型服務例項的提供。

public interface IFoo {}
public interface IBar {}
public interface IBaz {} 
public interface IGux {}
public interface IFoobar<T1, T2> {}
public class Base : IDisposable
{
    public Base() => Console.WriteLine($"Instance of {GetType().Name} is created.");
    public void Dispose()  => Console.WriteLine($"Instance of {GetType().Name} is disposed.");
}

public class Foo : Base, IFoo{ }
public class Bar : Base, IBar{ }
public class Baz : Base, IBaz{ } 
[MapTo(typeof(IGux), Lifetime.Root)]
public class Gux : Base, IGux { }
public class Foobar<T1, T2>: IFoobar<T1,T2>
{
    public IFoo Foo { get; }
    public IBar Bar { get; }
    public Foobar(IFoo foo, IBar bar)
    {
        Foo = foo;
        Bar = bar;
    }
}

在如下所示的程式碼片段中我們建立了一個Cat物件並採用上面提到的方式針對介面IFoo、IBar和IBaz註冊了對應的服務,它們採用的生命週期模式分別為Transient、Self和Root。然後我們還呼叫了另一個將當前入口程式集作為引數的Register方法,該方法會解析指定程式集中標註了MapToAttribute特性的型別並作相應的服務註冊,對於我們演示的程式來,該方法會完成針對IGux/Gux型別的服務註冊。接下來我們利用Cat物件建立了它的兩個子容器,並呼叫子容器的GetService<T>方法提供相應的服務例項。

class Program
{
    static void Main()
    {
        var root = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar>(_=> new Bar(), Lifetime.Self) 
            .Register<IBaz, Baz>(Lifetime.Root) 
            .Register(Assembly.GetEntryAssembly());
        var cat1 = root.CreateChild();
        var cat2 = root.CreateChild();

        void GetServices<TService>(Cat cat)
        {
            cat.GetService<TService>();
            cat.GetService<TService>();
        }

        GetServices<IFoo>(cat1);
        GetServices<IBar>(cat1);
        GetServices<IBaz>(cat1); 
        GetServices<IGux>(cat1);
        Console.WriteLine();
        GetServices<IFoo>(cat2);
        GetServices<IBar>(cat2);
        GetServices<IBaz>(cat2); 
        GetServices<IGux>(cat2);
    }
}

上面的程式執行之後會在控制檯上輸出如圖3-7所示的結果,輸出的內容不僅表明Cat能夠根據新增的服務註冊提供對應型別的服務例項,還體現了它對生命週期的控制。由於服務IFoo被註冊為Transient服務,所以Cat針對該介面的服務提供四次請求都會建立一個全新的Foo物件。IBar服務的生命週期模式為Self,如果我們利用同一個Cat物件來提供對應的服務例項,該Cat物件只會建立一個Bar物件,所以整個過程中會建立兩個Bar物件。IBaz和IGux服務採用Root生命週期,所以具有同根的兩個Cat物件提供的總是同一個Baz/Gux物件,後者只會被建立一次。

除了提供類似於IFoo、IBar和IBaz這樣非泛型的服務例項之外,如果具有針對泛型定義(Generic Definition)的服務註冊,Cat同樣也能提供泛型服務例項。如下面的程式碼片段所示,在為建立的Cat物件添加了針對IFoo和IBar介面的服務註冊之後,我們呼叫Register方法註冊了針對泛型定義IFoobar<,>的服務註冊,具體的實現型別為Foobar<,>。當我們利用Cat物件提供一個型別為IFoobar<IFoo, IBar>的服務例項的時候,它會建立並返回一個Foobar<Foo, Bar>物件。

public class Program
{
    public static void Main()
    {
        var cat = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar, Bar>(Lifetime.Transient)
            .Register(typeof(IFoobar<,>), typeof(Foobar<,>), Lifetime.Transient);

        var foobar = (Foobar<IFoo, IBar>)cat.GetService<IFoobar<IFoo, IBar>>();
        Debug.Assert(foobar.Foo is Foo);
        Debug.Assert(foobar.Bar is Bar);
    }
}

當我們在進行服務註冊的時候,可以為同一個型別新增多個服務註冊。雖然新增的所有服務註冊均是有效的,不過由於擴充套件方法GetService<TService>總是返回一個唯一的服務例項,我們對該方法採用了“後來居上”的策略,即總是採用最近新增的服務註冊來建立服務例項。如果我們呼叫另一個擴充套件方法GetServices<TService>,它將利用返回根據所有服務註冊提供的服務例項。

如下面的程式碼片段所示,我們為建立的Cat物件添加了三個針對Base型別的服務註冊,對應的實現型別分別為Foo、Bar和Baz。我們最後將Base作為泛型引數呼叫了GetServices<Base>方法,該方法會返回包含三個Base物件的集合,集合元素的型別分別為Foo、Bar和Baz。

public class Program
{
    public static void Main()
    {
        var services = new Cat()
            .Register<Base, Foo>(Lifetime.Transient)
            .Register<Base, Bar>(Lifetime.Transient)
            .Register<Base, Baz>(Lifetime.Transient)
            .GetServices<Base>();
        Debug.Assert(services.OfType<Foo>().Any());
        Debug.Assert(services.OfType<Bar>().Any());
        Debug.Assert(services.OfType<Baz>().Any());
    }
}

如果提供的服務例項實現了IDisposable介面,我們應該在適當的時候呼叫其Dispose方法釋放該服務例項。由於服務例項的生命週期完全由作為依賴注入容器的Cat物件來管理,那麼通過呼叫Dispose方法來釋放服務例項自然也應該由它來負責。Cat針對提供服務例項的釋放策略取決於採用的生命週期模式,具體的策略如下:

  • Transient和Self:所有實現了IDisposable介面的服務例項會被當前Cat物件儲存起來,當Cat物件自身的Dispose方法被呼叫的時候,這些服務例項的Dispose方法會隨之被呼叫。
  • Root:由於服務例項儲存在作為根容器的Cat物件上,所以當這個Cat物件的Dispose方法被呼叫的時候,這些服務例項的Dispose方法會隨之被呼叫。

上述的釋放策略可以通過如下的演示例項來印證。我們在如下的程式碼片段中建立了一個Cat物件,並添加了相應的服務註冊。我們接下來呼叫了CreateChild方法建立代表子容器的Cat物件,並用它提供了四個註冊服務對應的例項。

class Program
{
    static void Main()
    {
        using (var root = new Cat()
            .Register<IFoo, Foo>(Lifetime.Transient)
            .Register<IBar>(_ => new Bar(), Lifetime.Self)
            .Register<IBaz, Baz>(Lifetime.Root)
            .Register(Assembly.GetEntryAssembly()))
        {
            using (var cat = root.CreateChild())
            {
                cat.GetService<IFoo>();
                cat.GetService<IBar>();
                cat.GetService<IBaz>();
                cat.GetService<IGux>();
                Console.WriteLine("Child cat is disposed.");
            }
            Console.WriteLine("Root cat is disposed.");
        }
    }
}

由於兩個Cat物件的建立都是在using塊中進行的,所以它們的Dispose方法都會在using塊結束的地方被呼叫。為了確定方法被呼叫的時機,我們特意在控制檯上列印了相應的文字。該程式執行之後會在控制檯上輸出如下圖所示的結果,我們可以看到當作為子容器的Cat物件的Dispose方法被呼叫的時候,由它提供的兩個生命週期模式分別為Transient和Self的兩個服務例項(Foo和Bar)被正常釋放了。至於生命週期模式為Root的服務例項Baz和Gux,它的Dispose方法會延遲到作為根容器的Cat物件的Dispose方法被呼叫的時候。

二、設計與實現

在完成針對Cat的程式設計體驗之後,我們來聊聊這個依賴注入容器的設計原理和具體實現。由於作為依賴注入容器的Cat物件總是利用預先新增的服務註冊來提供對應的服務例項,所以服務註冊至關重要。如下所示的就是表示服務註冊的ServiceRegistry的定義,它具有三個核心屬性(ServiceType、Lifetime和Factory),分別代表服務型別、生命週期模式和用來建立服務例項的工廠。最終用來建立服務例項的工廠體現為一個型別為Func<Cat,Type[], object>的委託物件,它的兩個輸入分別代表當前使用的Cat物件以及提供服務型別的泛型引數,如果提供的服務型別並不是一個泛型型別,這個引數被會指定為一個空的陣列。

public class ServiceRegistry
{
    public Type ServiceType { get; }
    public Lifetime Lifetime { get; }
    public Func<Cat,Type[], object> actory { get; }
    internal ServiceRegistry Next { get; set; }

    public ServiceRegistry(Type serviceType, Lifetime lifetime,   Func<Cat,Type[], object> factory)
    {
        ServiceType     = serviceType;
        Lifetime     = lifetime;
        Factory     = factory;
    }

    internal IEnumerable<ServiceRegistry> AsEnumerable()
    {
        var list = new List<ServiceRegistry>();
        for (var self = this; self!=null; self= self.Next)
        {
            list.Add(self);
        }
        return list;
    }
}

我們將針對同一個服務型別(ServiceType屬性相同)的多個ServiceRegistry組成一個連結串列,作為相鄰節點的兩個ServiceRegistry物件通過Next屬性關聯起來。我們為ServiceRegistry定義了一個AsEnumerable方法使它返回由當前以及後續節點組成的ServiceRegistry集合。如果當前ServiceRegistry為連結串列頭,那麼這個方法會返回連結串列上的所有ServiceRegistry物件。下圖體現了服務註冊核心三要素和連結串列結構。

在瞭解了表示服務註冊的ServiceRegistry之後,我們來著重介紹表示依賴注入容器的Cat型別。如下面的程式碼片段所示,Cat同時實現了IServiceProvider和IDisposable介面,定義在前者中的GetService方法用於提供服務例項。作為根容器的Cat物件通過公共建構函式建立,另一個內部建構函式則用來建立作為子容器的Cat物件,指定的Cat物件將作為父容器。

public class Cat : IServiceProvider, IDisposable
{
    internal readonly Cat                             _root;
    internal readonly ConcurrentDictionary<Type, ServiceRegistry>     _registries;
    private readonly ConcurrentDictionary<Key, object>             _services;
    private readonly ConcurrentBag<IDisposable>                 _disposables;
    private volatile bool _disposed;

    public Cat()
    {
        _registries = new ConcurrentDictionary<Type, ServiceRegistry>();
        _root = this;
        _services = new ConcurrentDictionary<Key, object>();
        _disposables = new ConcurrentBag<IDisposable>();
    }

    internal Cat(Cat parent)
    {
        _root = parent._root;
        _registries = _root._registries;
        _services = new ConcurrentDictionary<Key, object>();
        _disposables = new ConcurrentBag<IDisposable>();
    }

    private void EnsureNotDisposed()
    {
        if (_disposed)
        {
            throw new ObjectDisposedException("Cat");
        }
    }
    ...
}

作為根容器的Cat物件通過_root欄位表示。_registries欄位返回的ConcurrentDictionary<Type, ServiceRegistry>物件用來儲存所有新增的服務註冊,該字典物件的Key和Value分別表示服務型別和ServiceRegistry連結串列,下圖體現這一對映關係。由於需要負責完成對提供服務例項的釋放工作,所以我們需要將實現了IDisposable介面的服務例項儲存在通過_disposables欄位表示的集合中。

由當前Cat物件提供的非Transient服務例項儲存在由_services欄位表示的一個ConcurrentDictionary<Key, object>物件上,該字典物件的鍵型別為如下所示的Key,它相當於是建立服務例項所使用的ServiceRegistry物件和泛型引數型別陣列的組合。

internal class Key : IEquatable<Key>
{
    public ServiceRegistry     Registry { get; }
    public Type[]         GenericArguments { get; }

    public Key(ServiceRegistry registry, Type[] genericArguments)
    {
        Registry  = registry;
        GenericArguments = genericArguments;
    }

    public bool Equals(Key other)
    {
        if (Registry != other.Registry)
        {
            return false;
        }
        if (GenericArguments.Length != other.GenericArguments.Length)
        {
            return false;
        }
        for (int index = 0; index < GenericArguments.Length; index++)
        {
            if (GenericArguments[index] != other.GenericArguments[index])
            {
                return false;
            }
        }
        return true;
    }

    public override int GetHashCode()
    {
        var hashCode = Registry.GetHashCode();
        for (int index = 0; index < GenericArguments.Length; index++)
        {
            hashCode ^= GenericArguments[index].GetHashCode();
        }
        return hashCode;
    }
    public override bool Equals(object obj) => obj is Key key ? Equals(key) : false;
}

雖然我們為Cat定義了若干擴充套件方法來提供多種不同的服務註冊,但是這些方法最終都會呼叫如下這個Register方法,該方法會將提供的ServiceRegistry新增到_registries欄位表示的字典物件中。值得注意的是,不論我們是呼叫哪個Cat物件的Register方法,指定的ServiceRegistry都會被新增到作為根容器的Cat物件上。

public class Cat : IServiceProvider, IDisposable
{
    public Cat Register(ServiceRegistry registry)
    {
        EnsureNotDisposed();
        if (_registries.TryGetValue(registry.ServiceType, out var existing))
        {
            _registries[registry.ServiceType] = registry;
            registry.Next = existing;
        }
        else
        {
            _registries[registry.ServiceType] = registry;
        }
        return this;
    }
    ...
}

用來提供服務例項的核心操作實現在如下這個GetServiceCore方法中。如下面的程式碼片段所示,我們在呼叫該方法的時候需要指定對應的ServiceRegistry物件的服務型別的泛型引數。當該方法被執行的時候,對於Transient的生命週期模式,它會直接利用ServiceRegistry提供的工廠來建立服務例項。如果服務例項的型別實現了IDisposable介面,該物件會被新增到_disposables欄位表示的待釋放服務例項列表中。對於Root和Self生命週期模式,該方法會先根據提供的ServiceRegistry判斷是否對應的服務例項已經存在,存在的服務例項會直接返回。

public class Cat : IServiceProvider, IDisposable
{
    private object GetServiceCore(ServiceRegistry registry, Type[] genericArguments)
    {
        var key = new Key(registry, genericArguments);
        var serviceType = registry.ServiceType;

        switch (registry.Lifetime)
        {
            case Lifetime.Root: return GetOrCreate(_root._services, _root._disposables);
            case Lifetime.Self: return GetOrCreate(_services, _disposables);
            default:
                {
                    var service = registry.Factory(this, genericArguments);
                    if (service is IDisposable disposable && disposable != this)
                    {
                        _disposables.Add(disposable);
                    }
                    return service;
                }
        }

        object GetOrCreate(ConcurrentDictionary<Key, object> services,  ConcurrentBag<IDisposable> disposables)
        {
            if (services.TryGetValue(key, out var service))
            {
                return service;
            }
            service = registry.Factory(this, genericArguments);
            services[key] = service;
            if (service is IDisposable disposable)
            {
                disposables.Add(disposable);
            }
            return service;
        }
    }
}

GetServiceCore方法只有在指定ServiceRegistry對應的服務例項不存在的情況下才會利用提供的工廠來建立服務例項,建立的服務例項會根據生命週期模式儲存到作為根容器的Cat物件或者當前Cat物件上。如果提供的服務例項實現了IDisposable介面,在採用Root生命週期模式下會被儲存到作為根容器的Cat物件的待釋放列表中。如果生命週期模式為Self,它會被新增到當前Cat物件的待釋放列表中。

在實現的GetService方法中,Cat會根據指定的服務型別找到對應的ServiceRegistry物件,並最終呼叫GetServiceCore方法來提供對應的服務例項。GetService方法還會解決一些特殊服務的提供問題,比如若服務型別為Cat或者IServiceProvider,該方法返回的就是它自己。如果服務型別為IEnumerable<T>,GetService方法會根據泛型引數型別T找到所有的ServiceRegistry並利用它們來建立對應的服務例項,最終返回的是由這些服務例項組成的集合。除了這些,針對泛型服務例項的提供也是在這個方法中解決的。

public class Cat : IServiceProvider, IDisposable
{
    public object GetService(Type serviceType)
    {
        EnsureNotDisposed();

        if (serviceType == typeof(Cat) || serviceType == typeof(IServiceProvider))
        {
            return this;
        }

        ServiceRegistry registry;
        //IEnumerable<T>
        if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() ==  typeof(IEnumerable<>))
        {
            var elementType = serviceType.GetGenericArguments()[0];
            if (!_registries.TryGetValue(elementType, out registry))
            {
                return Array.CreateInstance(elementType, 0);
            }

            var registries = registry.AsEnumerable();
            var services = registries.Select(it => GetServiceCore(it, Type.EmptyTypes)).ToArray();
            Array array = Array.CreateInstance(elementType, services.Length);
            services.CopyTo(array, 0);
            return array;
        }

        //Generic
        if (serviceType.IsGenericType && !_registries.ContainsKey(serviceType))
        {
            var definition = serviceType.GetGenericTypeDefinition();
            return _registries.TryGetValue(definition, out registry)
                ? GetServiceCore(registry, serviceType.GetGenericArguments())
                : null;
        }

        //Normal
        return _registries.TryGetValue(serviceType, out registry)
                ? GetServiceCore(registry, new Type[0])
                : null;
    }
    ...
}

在實現的Dispose方法中,由於所有待釋放的服務例項已經儲存到_disposables欄位表示的集合中,所以我們只需要依次呼叫它們的Dispose方法即可。在釋放了所有服務例項並清空待釋放列表後,Dispose還會清空_services欄位表示的服務例項列表。

public class Cat : IServiceProvider, IDisposable
{
    public void Dispose()
    {
        _disposed = true;
        foreach(var disposable in _disposables)
        {
            disposable.Dispose();
        }
        _disposables.Clear();
        _services.Clear();
    }
    ...
}

三、擴充套件方法

為了方便註冊服務,我們定義瞭如下六個Register擴充套件方法。由於服務註冊的新增總是需要呼叫Cat自身的Register方法來完成,所以這些方法最終都需要建立一個代表服務註冊的ServiceRegistry物件。對於一個ServiceRegistry物件來說,它最為核心的元素莫過於表示服務例項建立工廠的Func<Cat,Type[], object>物件,所以上述這六個擴充套件方法需要解決的就是建立這麼一個委託物件。

public static class CatExtensions
{
    public static Cat Register(this Cat cat, Type from, Type to, Lifetime lifetime)
    {
        Func<Cat, Type[], object> factory =  (_, arguments) => Create(_, to, arguments);
        cat.Register(new ServiceRegistry(from, lifetime, factory));
        return cat;
    }

    public static Cat Register<TFrom, TTo>(this Cat cat, Lifetime lifetime) where TTo:TFrom
        => cat. Register(typeof(TFrom), typeof(TTo), lifetime);

    public static Cat Register(this Cat cat, Type serviceType, object instance)
    {
        Func<Cat, Type[], object> factory = (_, arguments) => instance;
        cat.Register(new ServiceRegistry(serviceType, Lifetime.Root, factory));
        return cat;
    }

    public static Cat Register<TService>(this Cat cat, TService instance)
    {
        Func<Cat, Type[], object> factory = (_, arguments) => instance;
        cat.Register(new ServiceRegistry(typeof(TService),  Lifetime.Root,  factory));
        return cat;
    }

    public static Cat Register(this Cat cat, Type serviceType, 
    Func<Cat, object> factory, Lifetime lifetime)
    {
        cat.Register(new ServiceRegistry(serviceType, lifetime,  (_, arguments) => factory(_)));
        return cat;
    }

    public static Cat Register<TService>(this Cat cat, 
    Func<Cat,TService> factory, Lifetime lifetime)
    {
        cat.Register(new ServiceRegistry(typeof(TService), lifetime,  (_,arguments)=>factory(_)));
        return cat;
    }

    private static object Create(Cat cat, Type type, Type[] genericArguments)
    {
        if (genericArguments.Length > 0)
        {
            type = type.MakeGenericType(genericArguments);
        }
        var constructors = type.GetConstructors();
        if (constructors.Length == 0)
        {
            throw new InvalidOperationException($"Cannot create the instance of 
                {type} which does not have a public constructor.");
        }
        var constructor = constructors.FirstOrDefault(it =>   it.GetCustomAttributes(false).OfType<InjectionAttribute>().Any());
        constructor ??= constructors.First();
        var parameters = constructor.GetParameters();
        if (parameters.Length == 0)
        {
            return Activator.CreateInstance(type);
        }
        var arguments = new object[parameters.Length];
        for (int index = 0; index < arguments.Length; index++)
        {
            arguments[index] = cat.GetService(parameters[index].ParameterType);
        }
        return constructor.Invoke(arguments);
    }
}

由於前兩個過載指定的是服務實現型別,所以我們需要呼叫對應的建構函式來建立服務例項,這一邏輯實現在私有的Create方法中。第三個擴充套件方法指定的直接就是服務例項,所以我們很容易將提供的引數轉換成一個Func<Cat,Type[], object>。

我們刻意簡化了建構函式的篩選邏輯。為了解決建構函式的選擇問題,我們引入如下這個InjectionAttribute特性。我們將所有公共例項建構函式作為候選的建構函式,並會優先選擇標註了該特性的建構函式。當建構函式被選擇出來後,我們需要通過分析其引數型別並利用Cat物件來提供具體的引數值,這實際上是一個遞迴的過程。最終我們將針對建構函式的呼叫轉換成Func<Cat,Type[], object>物件,進而創建出表示服務註冊的ServiceRegistry物件。

[AttributeUsage( AttributeTargets.Constructor)]
public class InjectionAttribute: Attribute {}


前面給出的程式碼片段還提供了HasRegistry和HasRegistry<T>方法來確定指定型別的服務註冊是否存在。除此之外,用於提供服務例項的泛型方法GetService<T>和用於提供所有指定型別服務例項的GetServices<TService>方法採用瞭如下的定義方式。

public static class CatExtensions
{
    public static IEnumerable<TService> GetServices<T>(this Cat cat)  => cat.GetService<IEnumerable<TService >>();
    public static TService GetService<TService >(this Cat cat)  => (TService)cat.GetService(typeof(T));
}


上述六個擴充套件方法幫助我們完成針對單一服務的註冊,有時間我們的專案中可能會出現非常多的服務需要註冊,如何能夠完成針對它們的批量註冊會是不錯的選擇。我們的依賴注入框架提供了針對程式集範圍的批量服務註冊。為了標識帶註冊的服務,我們需要在服務實現型別上標註如下這個MapToAttribute型別,並指定服務型別(一般為它實現的介面或者繼承的基類)和生命週期。

[AttributeUsage( AttributeTargets.Class, AllowMultiple = true)]
public sealed class MapToAttribute: Attribute
{       
    public Type     ServiceType { get; }
    public Lifetime     Lifetime { get; }

    public MapToAttribute(Type serviceType, Lifetime lifetime)
    {
        ServiceType = serviceType;
        Lifetime = lifetime;
    }
}

針對程式集範圍的批量服務註冊實現在Cat的如下這個Register擴充套件方法中。如下面的程式碼片段所示,該方法會從指定程式集中獲取所有標註了MapToAttribute特性的型別,並提取出服務型別、實現型別和生命週期模型,然後利用它們批量完成所需的服務註冊。

public static class CatExtensions
{   
    public static Cat Register(this Cat cat, Assembly assembly)
    {
        var typedAttributes = from type in assembly.GetExportedTypes()
            let attribute = type.GetCustomAttribute<MapToAttribute>()
            where attribute != null
            select new { ServiceType = type, Attribute = attribute };
        foreach (var typedAttribute in typedAttributes)
        {
            cat.Register(typedAttribute.Attribute.ServiceType, 
                typedAttribute.ServiceType, typedAttribute.Attribute.Lifetime);
        }
        return cat;
    }
}

除了上述這七個用來註冊服務的Register擴充套件方法,我們還為Cat型別定義瞭如下兩個擴充套件方法,其中CreateService<T>方法以泛型引數的形式指定獲取服務例項對應註冊的型別,CreateServices<T>方法會提供指定服務型別的所有例項,而CreateChild方法則幫助我們建立一個代表子容器的Cat物件。

public static class CatExtensions
{   
    public static T GetService<T>(this Cat cat) => (T)cat.GetService(typeof(T));
    public static IEnumerable<T> GetServices<T>(this Cat cat) => cat.GetService<IEnumerable<T>>();
    public static Cat CreateChild(this Cat cat) => new Cat(cat);
}

[ASP.NET Core 3框架揭祕] 依賴注入:控制反轉
[ASP.NET Core 3框架揭祕] 依賴注入:IoC模式
[ASP.NET Core 3框架揭祕] 依賴注入:依賴注入模式
[ASP.NET Core 3框架揭祕] 依賴注入:一個迷你版DI