本系列文章旨在剖析.NET Core的依賴注入框架的實現原理,到目前為止我們通過三篇文章(《控制反轉》、《基於IoC的設計模式》和《 依賴注入模式》)從純理論的角度對依賴注入進行了深入論述,為了讓讀者朋友能夠更好地理解.NET Core的依賴注入框架的設計思想和實現原理,我們創建了一個簡易版本的DI框架,也就是我們在前面文章中多次提及的Cat。我們會上下兩篇來介紹這個被稱為為Cat的DI框架,上篇介紹編程模型,下篇關注設計實現。[源代碼從這里下載]
目錄
一、DI容器的層次結構與服務實例生命周期
二、服務的注冊於提取
三、提供泛型服務
四、多服務實例的提取
五、服務實例的釋放回收
一、DI容器的層次結構與服務實例生命周期
雖然我們對這個名為Cat的DI框架進行了最大限度的簡化,但是與.NET Core的真實DI框架相比,Cat不僅采用了一致的設計,而且幾乎具備了后者所有的功能特性。作為DI容器的Cat對象不僅僅是作為服務實例的提供者,它同時還需要維護提供服務實例的生命周期。Cat提供了三種生命周期模式,如果要了解它們之間的差異,就必需對多個Cat之間的層次關系有充分的認識。一個代表DI容器的Cat用以來創建多個新的Cat對象,后者視前者為“父容器”,所以多個Cat對象通過其“父子關系”維系一個樹形層次化結構。不過着僅僅是一個邏輯結構而已,實際上每個Cat對象只會按照圖1所示的方式引用整棵樹的根。
在了解了代表DI容器的多個Cat對象之間的關系之后,對於三種預定義的生命周期模式就很好理解了。如下所示的Lifetime枚舉代表着三種生命周期模式,其中Transient代表容器針對每次服務請求都會創建一個新的服務實例,它代表一種“即用即取,用完即棄”的消費方式;而Self則是將提供服務實例保存在當前容器中,它代表針對某個容器的單例模式; Root則是將每個容器提供的服務實例統一存放到根容器中,所以該模式能夠在多個“同根”容器范圍內確保提供的服務是單例的。
public enum Lifetime { Root, Self, Transient }
- 指定注冊服務的實現類型;
- 指定一個現有的服務實例;
- 指定一個創建服務實例的工廠。
二、服務的注冊於提取
我們定義了如下的接口和對應的實現類型來演示針對Cat的服務注冊和提取。其中Foo、Bar和Baz分別實現了對應的接口IFoo、IBar和IBaz,為了反映Cat對服務實例生命周期的控制,我們讓它們派生於同一個基類Base。Base實現了IDisposable接口,我們在其構造函數和實現的Dispose方法中打印出相應的文字以確定對應的實例何時被創建和釋放。我們還定義了一個泛型的接口IFoobar<T1, T2>和對應的實現類Foobar<T1, T2>來演示Cat針對泛型服務實例的提供。
public interface IFoo {} public interface IBar {} public interface IBaz {} public interface IFoobar<T1, T2> {} public class Base : IDisposable { public Base() => Console.WriteLine($"An instance of {GetType().Name} is created."); public void Dispose() => Console.WriteLine($"The instance of {GetType().Name} is disposed."); } public class Foo : Base, IFoo, IDisposable { } public class Bar : Base, IBar, IDisposable { } public class Baz : Base, IBaz, IDisposable { } 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。接下來我們利用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); 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); Console.WriteLine(); GetServices<IFoo>(cat2); GetServices<IBar>(cat2); GetServices<IBaz>(cat2); } }
三、提供泛型服務
除了提供類似於IFoo、IBar和IBaz這樣非泛型服務實例之外,如果具有對應的泛型定義(Generic Definition)的服務注冊,Cat同樣也能提供泛型服務實例。如下面的代碼片段所示,在為創建的Cat對象添加了針對IFoo和IBar接口的服務注冊之后,我們調用Register方法注冊了針對泛型定義IFoobar<,>的服務注冊,實現的類型為Foobar<,>。當我們利用該Cat對象提供一個類型為IFoobar<IFoo, IBar>的服務實例的時候,它會創建並返回一個Foobar<Foo, Bar>對象。
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<T>總是返回一個唯一的服務實例,我們對該方法采用了“后來居上”的策略,即總是采用最近添加的服務注冊來創建服務實例。如果我們調用另一個擴展方法GetServices<T>,它將利用返回所有服務注冊提供的服務實例。
如下面的代碼片段所示,我們為創建的Cat對象添加了三個針對Base類型的服務注冊,對應的實現類型分別為Foo、Bar和Baz。我們最后將Base作為泛型參數調用了GetServices<Base>方法,該方法會返回包含三個Base對象的集合,集合元素的類型分別為Foo、Bar和Baz。
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方法釋放該服務實例。由於服務實例的生命周期完全由作為DI容器的Cat對象來管理,通過調用Dispose方法來釋放服務實例自然也應該由它來負責。Cat針對提供服務實例的釋放策略取決於對應的服務注冊采用的生命周期模式,具體的策略如下:
Transient和Self:所有實現了IDisposable接口的服務實例會被作為服務提供者的當前Cat對象保存起來,當Cat對象自身的Dispose方法被調用的時候,這些服務實例的Dispose方法會隨之被調用。
Root:由於服務實例保存在作為根容器的Cat對象上,所以后者的Dispose方法的調用會觸發針對服務實例的釋放。
上述的釋放策略可以通過如下的演示實例來印證。我們在如下的代碼片段中創建了一個Cat對象,並添加了針對IFoo、IBar和IBaz的服務注冊。接下來我們調用了CreateChild方法創建代碼子容器的Cat對象,並用后者提供了三個注冊服務對應的實例。
class Program { static void Main() { using (var root = new Cat() .Register<IFoo, Foo>(Lifetime.Transient) .Register<IBar, Bar>(Lifetime.Self) .Register<IBaz, Baz>(Lifetime.Root)) { using (var cat = root.CreateChild()) { cat.GetService<IFoo>(); cat.GetService<IBar>(); cat.GetService<IBaz>(); Console.WriteLine("Child cat is disposed."); } Console.WriteLine("Root cat is disposed."); } } }
依賴注入[1]: 控制反轉
依賴注入[2]: 基於IoC的設計模式
依賴注入[3]: 依賴注入模式
依賴注入[4]: 創建一個簡易版的DI框架[上篇]
依賴注入[5]: 創建一個簡易版的DI框架[下篇]
依賴注入[6]: .NET Core DI框架[編程體驗]
依賴注入[7]: .NET Core DI框架[服務注冊]
依賴注入[8]: .NET Core DI框架[服務消費]