ASP.NET Core中的依賴注入(4): 構造函數的選擇與服務生命周期管理

ServiceProvider最終提供的服務實例都是根據對應的ServiceDescriptor創建的,對于一個具體的ServiceDescriptor對象來說,如果它的ImplementationInstance和ImplementationFactory屬性均為Null,那么ServiceProvider最終會利用其ImplementationType屬性返回的真實類型選擇一個適合的構造函數來創建最終的服務實例。我們知道服務服務的真實類型可以定義了多個構造函數,那么ServiceProvider針對構造函數的選擇會采用怎樣的策略呢?

目錄
一、構造函數的選擇
二、生命周期管理
??? ServiceScope與ServiceScopeFactory
??? 三種生命周期管理模式
??? 服務實例的回收

一、構造函數的選擇

如果ServiceProvider試圖通過調用構造函數的方式來創建服務實例,傳入構造函數的所有參數必須先被初始化,最終被選擇出來的構造函數必須具備一個基本的條件:ServiceProvider能夠提供構造函數的所有參數。為了讓讀者朋友能夠更加真切地理解ServiceProvider在構造函數選擇過程中采用的策略,我們不讓也采用實例演示的方式來進行講解。

我們在一個控制臺應用中定義了四個服務接口(IFoo、IBar、IBaz和IGux)以及實現它們的四個服務類(Foo、Bar、Baz和Gux)。如下面的代碼片段所示,我們為Gux定義了三個構造函數,參數均為我們定義了服務接口類型。為了確定ServiceProvider最終選擇哪個構造函數來創建目標服務實例,我們在構造函數執行時在控制臺上輸出相應的指示性文字。

   1: public?interface IFoo {}
   2: public?interface IBar {}
   3: public?interface IBaz {}
   4: public?interface IGux {}
   5:? 
   6: public?class Foo : IFoo {}
   7: public?class Bar : IBar {}
   8: public?class Baz : IBaz {}
   9: public?class Gux : IGux
  10: {
  11:???? public Gux(IFoo foo)
  12:???? {
  13:???????? Console.WriteLine("Gux(IFoo)");
  14:???? }
  15:? 
  16:???? public Gux(IFoo foo, IBar bar)
  17:???? {
  18:???????? Console.WriteLine("Gux(IFoo, IBar)");
  19:???? }
  20:? 
  21:???? public Gux(IFoo foo, IBar bar, IBaz baz)
  22:???? {
  23:???????? Console.WriteLine("Gux(IFoo, IBar, IBaz)");
  24:???? }
  25: }

我們在作為程序入口的Main方法中創建一個ServiceCollection對象并在其中添加針對IFoo、IBar以及IGux這三個服務接口的服務注冊,針對服務接口IBaz的注冊并未被添加。我們利用由它創建的ServiceProvider來提供針對服務接口IGux的實例,究竟能否得到一個Gux對象呢?如果可以,它又是通過執行哪個構造函數創建的呢?

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {?????? 
   5:???????? new ServiceCollection()
   6:???????????? .AddTransient<IFoo, Foo>()
   7:???????????? .AddTransient<IBar, Bar>()
   8:???????????? .AddTransient<IGux, Gux>()
   9:???????????? .BuildServiceProvider()
  10:???????????? .GetServices<IGux>();
  11:???? }
  12: }

對于定義在Gux中的三個構造函數來說,ServiceProvider所在的ServiceCollection包含針對接口IFoo和IBar的服務注冊,所以它能夠提供前面兩個構造函數的所有參數。由于第三個構造函數具有一個類型為IBaz的參數,這無法通過ServiceProvider來提供。根據我們上面介紹的第一個原則(ServiceProvider能夠提供構造函數的所有參數),Gux的前兩個構造函數會成為合法的候選構造函數,那么ServiceProvider最終會選擇哪一個呢?

在所有合法的候選構造函數列表中,最終被選擇出來的構造函數具有這么一個特征:每一個候選構造函數的參數類型集合都是這個構造函數參數類型集合的子集。如果這樣的構造函數并不存在,一個類型為InvalidOperationException的異常會被跑出來。根據這個原則,Gux的第二個構造函數的參數類型包括IFoo和IBar,而第一個構造函數僅僅具有一個類型為IFoo的參數,最終被選擇出來的會是Gux的第二個構造函數,所有運行我們的實例程序將會在控制臺上產生如下的輸出結果。

   1: Gux(IFoo, IBar)

接下來我們對實例程序略加改動。如下面的代碼片段所示,我們只為Gux定義兩個構造函數,它們都具有兩個參數,參數類型分別為IFoo&IBar和IBar&IBaz。在Main方法中,我們將針對IBaz/Baz的服務注冊添加到創建的ServiceCollection上。

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {?????? 
   5:???????? new ServiceCollection()
   6:???????????? .AddTransient<IFoo, Foo>()
   7:???????????? .AddTransient<IBar, Bar>()
   8:???????????? .AddTransient<IBaz, Baz>()
   9:???????????? .AddTransient<IGux, Gux>()
  10:???????????? .BuildServiceProvider()
  11:???????????? .GetServices<IGux>();
  12:???? }
  13: }
  14:? 
  15: public?class Gux : IGux
  16: {
  17:???? public Gux(IFoo foo, IBar bar) {}
  18:???? public Gux(IBar bar, IBaz baz) {}
  19: }

對于Gux的兩個構造函數,雖然它們的參數均能夠由ServiceProvider來提供,但是并沒有一個構造函數的參數類型集合能夠成為所有有效構造函數參數類型集合的超集,所以ServiceProvider無法選擇出一個最佳的構造函數。如果我們運行這個程序,一個InvalidOperationException異常會被拋出來,控制臺上將呈現出如下所示的錯誤消息。

   1: Unhandled Exception: System.InvalidOperationException: Unable to activate type 'Gux'. The following constructors are ambigious:
   2: Void .ctor(IFoo, IBar)
   3: Void .ctor(IBar, IBaz)
   4: ...

二、生命周期管理

生命周期管理決定了ServiceProvider采用怎樣的方式創建和回收服務實例。ServiceProvider具有三種基本的生命周期管理模式,分別對應著枚舉類型ServiceLifetime的三個選項(Singleton、Scoped和Transient)。對于ServiceProvider支持的這三種生命周期管理模式,Singleton和Transient的語義很明確,前者(Singleton)表示以“單例”的方式管理服務實例的生命周期,意味著ServiceProvider對象多次針對同一個服務類型所提供的服務實例實際上是同一個對象;而后者(Transient)則完全相反,對于每次服務提供請求,ServiceProvider總會創建一個新的對象。那么Scoped又體現了ServiceProvider針對服務實例怎樣的生命周期管理方式呢?

ServiceScope與ServiceScopeFactory

ServiceScope為某個ServiceProvider對象圈定了一個“作用域”,枚舉類型ServiceLifetime中的Scoped選項指的就是這么一個ServiceScope。在依賴注入的應用編程接口中,ServiceScope通過一個名為IServiceScope的接口來表示。如下面的代碼片段所示,繼承自IDisposable接口的IServiceScope具有一個唯一的只讀屬性ServiceProvider返回確定這個服務范圍邊界的ServiceProvider。表示ServiceScope由它對應的工廠ServiceScopeFactory來創建,后者體現為具有如下定義的接口IServiceScopeFactory。

   1: public?interface IServiceScope : IDisposable
   2: {
   3:???? IServiceProvider ServiceProvider { get; }
   4: }
   5:? 
   6: public?interface IServiceScopeFactory
   7: {
   8:???? IServiceScope CreateScope();
   9: }

若要充分理解ServiceScope和ServiceProvider之間的關系,我們需要簡單了解一下ServiceProvider的層級結構。除了直接通過一個ServiceCollection對象創建一個獨立的ServiceProvider對象之外,一個ServiceProvider還可以根據另一個ServiceProvider對象來創建,如果采用后一種創建方式,我們指定的ServiceProvider與創建的ServiceProvider將成為一種“父子”關系。

   1: internal?class ServiceProvider : IServiceProvider, IDisposable
   2: {
   3:???? private?readonly ServiceProvider _root;
   4:???? internal ServiceProvider(ServiceProvider parent)
   5:???? {
   6:???????? _root = parent._root;
   7:???? }
   8:???? //其他成員
   9: }

3-11雖然在ServiceProvider在創建過程中體現了ServiceProvider之間存在著一種樹形化的層級結構,但是ServiceProvider對象本身并沒有一個指向“父親”的引用,它僅僅會保留針對根節點的引用。如上面的代碼片段所示,針對根節點的引用體現為ServiceProvider類的字段_root。當我們根據作為“父親”的ServiceProvider創建一個新的ServiceProvider的時候,父子均指向同一個“根”。我們可以將創建過程中體現的層級化關系稱為“邏輯關系”,而將ServiceProvider對象自身的引用關系稱為“物理關系”,右圖清楚地揭示了這兩種關系之間的轉化。

由于ServiceProvider自身是一個內部類型,我們不能采用調用構造函數的方式根據一個作為“父親”的ServiceProvider創建另一個作為“兒子”的ServiceProvider,但是這個目的可以間接地通過創建ServiceScope的方式來完成。如下面的代碼片段所示,我們首先創建一個獨立的ServiceProvider并調用其GetService<T>方法獲得一個ServiceScopeFactory對象,然后調用后者的CreateScope方法創建一個新的ServiceScope,它的ServiceProvider就是前者的“兒子”。

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {
   5:???????? IServiceProvider serviceProvider1 = new ServiceCollection().BuildServiceProvider();
   6:???????? IServiceProvider serviceProvider2 = serviceProvider1.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
   7:? 
   8:???????? object root = serviceProvider2.GetType().GetField("_root", BindingFlags.Instance| BindingFlags.NonPublic).GetValue(serviceProvider2);
   9:???????? Debug.Assert(object.ReferenceEquals(serviceProvider1, root));??????? 
  10:???? }
  11: }

如果讀者朋友們希望進一步了解ServiceScope的創建以及它和ServiceProvider之間的關系,我們不妨先來看看作為IServiceScope接口默認實現的內部類型ServiceScope的定義。如下面的代碼片段所示,ServiceScope僅僅是對一個ServiceProvider對象的簡單封裝而已。值得一提的是,當ServiceScope的Dispose方法被調用的時候,這個被封裝的ServiceProvider的同名方法同時被執行。

   1: {
   2:???? private?readonly ServiceProvider _scopedProvider;
   3:???? public ServiceScope(ServiceProvider scopedProvider)
   4:???? {
   5:???????? this._scopedProvider = scopedProvider;
   6:???? }
   7:? 
   8:???? public?void Dispose()
   9:???? {
  10:???????? _scopedProvider.Dispose();
  11:???? }
  12:? 
  13:???? public IServiceProvider ServiceProvider
  14:???? {
  15:???????? get {return _scopedProvider; }
  16:???? }
  17: }

IServiceScopeFactory接口的默認實現類型是一個名為ServiceScopeFactory的內部類型。如下面的代碼片段所示,ServiceScopeFactory的只讀字段“_provider”表示當前的ServiceProvider。當CreateScope方法被調用的時候,這個ServiceProvider的“子ServiceProvider”被創建出來,并被封裝成返回的ServiceScope對象。

   1: internal?class ServiceScopeFactory : IServiceScopeFactory
   2: {
   3:???? private?readonly ServiceProvider _provider;
   4:???? public ServiceScopeFactory(ServiceProvider provider)
   5:???? {
   6:???????? _provider = provider;
   7:???? }
   8:? 
   9:???? public IServiceScope CreateScope()
  10:???? {
  11:???????? return?new ServiceScope(new ServiceProvider(_provider));
  12:???? }
  13: }

三種生命周期管理模式

只有在充分了解ServiceScope的創建過程以及它與ServiceProvider之間的關系之后,我們才會對ServiceProvider支持的三種生命周期管理模式(Singleton、Scope和Transient)具有深刻的認識。就服務實例的提供方式來說,它們之間具有如下的差異:

  • Singleton:ServiceProvider創建的服務實例保存在作為根節點的ServiceProvider上,所有具有同一根節點的所有ServiceProvider提供的服務實例均是同一個對象。
  • Scoped:ServiceProvider創建的服務實例由自己保存,所以同一個ServiceProvider對象提供的服務實例均是同一個對象。
  • Transient:針對每一次服務提供請求,ServiceProvider總是創建一個新的服務實例。

為了讓讀者朋友們對ServiceProvider支持的這三種不同的生命周期管理模式具有更加深刻的理解,我們照例來做一個簡單的實例演示。我們在一個控制臺應用中定義了如下三個服務接口(IFoo、IBar和IBaz)以及分別實現它們的三個服務類(Foo、Bar和Baz)。

   1: public?interface IFoo {}
   2: public?interface IBar {}
   3: public?interface IBaz {}
   4:? 
   5: public?class Foo : IFoo {}
   6: public?class Bar : IBar {}
   7: public?class Baz : IBaz {}

現在我們在作為程序入口的Main方法中創建了一個ServiceCollection對象,并采用不同的生命周期管理模式完成了針對三個服務接口的注冊(IFoo/Foo、IBar/Bar和IBaz/Baz分別Transient、Scoped和Singleton)。我們接下來針對這個ServiceCollection對象創建了一個ServiceProvider(root),并采用創建ServiceScope的方式創建了它的兩個“子ServiceProvider”(child1和child2)。

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {
   5:???????? IServiceProvider root = new ServiceCollection()
   6:???????????? .AddTransient<IFoo, Foo>()
   7:???????????? .AddScoped<IBar, Bar>()
   8:???????????? .AddSingleton<IBaz, Baz>()
   9:???????????? .BuildServiceProvider();
  10:???????? IServiceProvider child1 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  11:???????? IServiceProvider child2 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  12:? 
  13:???????? Console.WriteLine("ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>() = {0}",ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>()));
  14:???????? Console.WriteLine("ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>() = {0}",ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>()));
  15:???????? Console.WriteLine("ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>() = {0}",ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>()));
  16:???????? Console.WriteLine("ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>() = {0}",ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>()));
  17:???? }
  18: }

為了驗證ServiceProvider針對Transient模式是否總是創建新的服務實例,我們利用同一個ServiceProvider(root)獲取針對服務接口IFoo的實例并進行比較。為了驗證ServiceProvider針對Scope模式是否僅僅在當前ServiceScope下具有“單例”的特性,我們先后比較了同一個ServiceProvider(child1)和不同ServiceProvider(child1和child2)兩次針對服務接口IBar獲取的實例。為了驗證具有“同根”的所有ServiceProvider針對Singleton模式總是返回同一個服務實例,我們比較了兩個不同child1和child2兩次針對服務接口IBaz獲取的服務實例。如下所示的輸出結構印證了我們上面的論述。

   1: ReferenceEquals(root.GetService<IFoo>(), root.GetService<IFoo>()???????? = False
   2: ReferenceEquals(child1.GetService<IBar>(), child1.GetService<IBar>()???? = True
   3: ReferenceEquals(child1.GetService<IBar>(), child2.GetService<IBar>()???? = False
   4: ReferenceEquals(child1.GetService<IBaz>(), child2.GetService<IBaz>()???? = True

服務實例的回收

ServiceProvider除了為我們提供所需的服務實例之外,對于由它提供的服務實例,它還肩負起回收之責。這里所說的回收與.NET自身的垃圾回收機制無關,僅僅針對于自身類型實現了IDisposable接口的服務實例,所謂的回收僅僅體現為調用它們的Dispose方法。ServiceProvider針對服務實例所采用的收受策略取決于服務注冊時采用的生命周期管理模式,具體采用的服務回收策略主要體現為如下兩點:

  • 如果注冊的服務采用Singleton模式,由某個ServiceProvider提供的服務實例的回收工作由作為根的ServiceProvider負責,后者的Dispose方法被調用的時候,這些服務實例的Dispose方法會自動執行。
  • 如果注冊的服務采用其他模式(Scope或者Transient),ServiceProvider自行承擔由它提供的服務實例的回收工作,當它的Dispose方法被調用的時候,這些服務實例的Dispose方法會自動執行。

我們照例使用一個簡單的實例來演示ServiceProvider針對不同生命周期管理模式所采用的服務回收策略。我們在一個控制臺應用中定義了如下三個服務接口(IFoo、IBar和IBaz)以及三個實現它們的服務類(Foo、Bar和Baz),這些類型具有相同的基類Disposable。Disposable實現了IDisposable接口,我們在Dispose方法中輸出相應的文字以確定對象回收的時機。

   1: public?interface IFoo {}
   2: public?interface IBar {}
   3: public?interface IBaz {}
   4:? 
   5: public?class Foo : Disposable, IFoo {}
   6: public?class Bar : Disposable, IBar {}
   7: public?class Baz : Disposable, IBaz {}
   8:? 
   9: public?class Disposable : IDisposable
  10: {
  11:???? public?void Dispose()
  12:???? {
  13:???????? Console.WriteLine("{0}.Dispose()", this.GetType());
  14:???? }
  15: }

我們在作為程序入口的Main方法中創建了一個ServiceCollection對象,并在其中采用不同的生命周期管理模式注冊了三個相應的服務(IFoo/Foo、IBar/Bar和IBaz/Baz分別采用Transient、Scoped和Singleton模式)。我們針對這個ServiceCollection創建了一個ServiceProvider(root),以及它的兩個“兒子”(child1和child2)。在分別通過child1和child2提供了兩個服務實例(child1:IFoo, child2:IBar/IBaz)之后,我們先后調用三個ServiceProvider(child1=>child2=>root)的Dispose方法。

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {
   5:???????? IServiceProvider root = new ServiceCollection()
   6:???????????? .AddTransient<IFoo, Foo>()
   7:???????????? .AddScoped<IBar, Bar>()
   8:???????????? .AddSingleton<IBaz, Baz>()
   9:???????????? .BuildServiceProvider();
  10:???????? IServiceProvider child1 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  11:???????? IServiceProvider child2 = root.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider;
  12:? 
  13:???????? child1.GetService<IFoo>();
  14:???????? child1.GetService<IFoo>();
  15:???????? child2.GetService<IBar>();
  16:???????? child2.GetService<IBaz>();
  17:? 
  18:???????? Console.WriteLine("child1.Dispose()");
  19:???????? ((IDisposable)child1).Dispose();
  20:? 
  21:???????? Console.WriteLine("child2.Dispose()");
  22:???????? ((IDisposable)child2).Dispose();
  23:? 
  24:???????? Console.WriteLine("root.Dispose()");
  25:???????? ((IDisposable)root).Dispose();
  26:???? }
  27: }

該程序運行之后會在控制臺上產生如下的輸出結果。從這個結果我們不難看出由child1提供的兩個采用Transient模式的服務實例的回收實在child1的Dispose方法執行之后自動完成的。當child2的Dispose方法被調用的時候,對于由它提供的兩個服務對象來說,只有注冊時采用Scope模式的Bar對象被自動回收了,至于采用Singleton模式的Baz對象的回收工作,是在root的Dispose方法被調用之后自動完成的。

   1: child1.Dispose()
   2: Foo.Dispose()
   3: Foo.Dispose()
   4: child2.Dispose()
   5: Bar.Dispose()
   6: root.Dispose()
   7: Baz.Dispose()

了解ServiceProvider針對不同生命周期管理模式所采用的服務回收策略還會幫助我們正確的使用它。具體來說,當我們在使用一個現有的ServiceProvider的時候,由于我們并不能直接對它實施回收(因為它同時會在其它地方被使用),如果直接使用它來提供我們所需的服務實例,由于這些服務實例可能會在很長一段時間得不到回收,進而導致一些內存泄漏的問題。如果所用的是一個與當前應用具有相同生命周期(ServiceProvider在應用終止的時候才會被回收)的ServiceProvider,而且提供的服務采用Transient模式,這個問題就更加嚴重了,這意味著每次提供的服務實例都是一個全新的對象,但是它永遠得不到回收。

為了解決這個問題,我想很多人會想到一種解決方案,那就是按照如下所示的方式顯式地對提供的每個服務實例實施回收工作。實際上這并不是一種推薦的編程方式,因為這樣的做法僅僅確保了服務實例對象的Dispose方法能夠被及時調用,但是ServiceProvider依然保持著對服務實例的引用,后者依然不能及時地被GC回收。

   1: public?void DoWork(IServiceProvider serviceProvider)
   2: {
   3:???? using (IFoobar foobar = serviceProvider.GetService<IFoobar>())
   4:???? {
   5:???????? ...
   6:???? }
   7: }

或者

   1: public?void DoWork(IServiceProvider serviceProvider)
   2: {
   3:???? IFoobar foobar = serviceProvider.GetService<IFoobar>();
   4:???? try
   5:???? {
   6:???????? ...
   7:???? }
   8:???? finally
   9:???? {
  10:???????? (foobar as IDisposable)?.Dispose();
  11:???? }
  12: }

由于提供的服務實例總是被某個ServiceProvider引用著[1](直接提供服務實例的ServiceProvider或者是它的根),所以服務實例能夠被GC從內存及時回收的前提是引用它的ServiceProvider及時地變成垃圾對象。要讓提供服務實例的ServiceProvider成為垃圾對象,我們就必須創建一個新的ServiceProvider,通過上面的介紹我們知道ServiceProvider的創建可以通過創建ServiceScope的方式來實現。除此之外,為我們可以通過回收ServiceScope的方式來回收對應的ServiceProvider,進而進一步回收由它提供的服務實例(僅限Transient和Scoped模式)。下面的代碼片段給出了正確的編程方式。

   1: public?void DoWork(IServiceProvider serviceProvider)
   2: {
   3:???? using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
   4:???? {
   5:???????? IFoobar foobar = serviceScope.ServiceProvider.GetService<IFoobar>();
   6:???????? ...
   7:???? }
   8: }

接下來我們通過一個簡單的實例演示上述這兩種針對服務回收的編程方式之間的差異。我們在一個控制臺應用中定義了一個繼承自IDisposable的服務接口IFoobar和實現它的服務類Foobar。如下面的代碼片段所示,為了確認對象真正被GC回收的時機,我們為Foobar定義了一個析構函數。在該析構函數和Dispose方法中,我們還會在控制臺上輸出相應的指導性文字。

   1: public?interface IFoobar: IDisposable
   2: {}
   3:? 
   4: public?class Foobar : IFoobar
   5: {
   6:???? ~Foobar()
   7:???? {
   8:???????? Console.WriteLine("Foobar.Finalize()");
   9:???? }
  10:? 
  11:???? public?void Dispose()
  12:???? {
  13:???????? Console.WriteLine("Foobar.Dispose()");
  14:???? }
  15: }

在作為程序入口的Main方法中,我們創建了一個ServiceCollection對象并采用Transient模式將IFoobbar/Foobar注冊其中。借助于通過該ServiceCollection創建的ServiceProvider,我們分別采用上述的兩種方式獲取服務實例并試圖對它實施回收。為了強制GC試試垃圾回收,我們顯式調用了GC的Collect方法。

   1: class Program
   2: {
   3:???? static?void Main(string[] args)
   4:???? {
   5:???????? IServiceProvider serviceProvider = new ServiceCollection()
   6:???????????? .AddTransient<IFoobar, Foobar>()
   7:???????????? .BuildServiceProvider();
   8:? 
   9:???????? serviceProvider.GetService<IFoobar>().Dispose();
  10:???????? GC.Collect();
  11:? 
  12:???????? Console.WriteLine("----------------");
  13:???????? using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope())
  14:???????? {
  15:???????????? serviceScope.ServiceProvider.GetService<IFoobar>();
  16:???????? }
  17:???????? GC.Collect();
  18:? 
  19:???????? Console.Read();
  20:???? }
  21: }

該程序執行之后會在控制臺上產生如下所示的輸出結果。從這個結果我們可以看出,如果我們使用現有的ServiceProvider來提供所需的服務實例,后者在GC進行垃圾回收之前并不會從內存中釋放。如果我們利用現有的ServiceProvider創建一個ServiceScope,并利用它所在的ServiceProvider來提供我們所需的服務實例,GC是可以將其從內存中釋放出來的。

   1: Foobar.Dispose()
   2: ----------------
   3: Foobar.Dispose()
   4: Foobar.Finalize()

[1] 對于分別采用 Scoped和Singleton模式提供的服務實例,當前ServiceProvider和根ServiceProvider分別具有對它們的引用。如果采用Transient模式,只有服務類型實現了IDisposable接口,當前ServiceProvider才需要對它保持引用以完成對它們的回收,否則沒有任何一個ServiceProvider保持對它們的引用。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)
ASP.NET Core中的依賴注入(2):依賴注入(DI)
ASP.NET Core中的依賴注入(3):服務注冊與提取
ASP.NET Core中的依賴注入(4):構造函數的選擇與生命周期管理
ASP.NET Core中的依賴注入(5):ServicePrvider實現揭秘【總體設計】
ASP.NET Core中的依賴注入(5):ServicePrvider實現揭秘【解讀ServiceCallSite】
ASP.NET Core中的依賴注入(5):ServicePrvider實現揭秘【補充漏掉的細節】


作者:蔣金楠?
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287385.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287385.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287385.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# WPF布局控件LayoutControl介紹

Dev學習地址文檔地址 &#xff1a;https://docs.devexpress.com/wpf&#xff1a;https://docs.devexpress.com/WPF/7875/wpf-controlswinform&#xff1a;https://docs.devexpress.com/WindowsForms/7874/winforms-controlsasp.NET: https://docs.devexpress.com/AspNet/7873/a…

《看聊天記錄都學不會C語言?太菜了吧》(7)下一篇文章告訴你牛郎是誰

若是大一學子或者是真心想學習剛入門的小伙伴可以私聊我&#xff0c;若你是真心學習可以送你書籍&#xff0c;指導你學習&#xff0c;給予你目標方向的學習路線&#xff0c;無套路&#xff0c;博客為證。 本系列文章將會以通俗易懂的對話方式進行教學&#xff0c;對話中將涵蓋…

【遙感物候】30年物候始期空間分布特征(平均值)和變化趨勢分析(Slope 一元線性回歸分析)

問題分析:本文的數據為經過預處理和計算得到的30年(1983-2012年)物候參數始期遙感數據,共計30期影像,現在需要逐像元計算整個物候始期的空間分布特征(平均值)和變化趨勢分析(Slope 一元線性回歸分析)。最終的效果(左圖為分布特征,右圖為變化趨勢): 一、方法原理 …

Android之CheckBox進行代碼設置setChecked(true)會觸發setOnCheckedChangeListener事件

1 問題 我們對CheckBox設置了setOnCheckedChangeListener監聽&#xff0c;代碼里面對CheckBox單獨代碼進行設置勾選(setChecked(true))的時候&#xff0c;會觸發OnCheckedChangeListener事件 2 解決辦法 用buttonView.isPressed()解決&#xff0c;這樣就只有手動點擊CheckBox…

密碼技術

要理解SSL就必須理解密碼系統、消息摘要函數(單向或散列函數)和數字簽名&#xff0c;這些技術是許多文獻所討論的主題(比如[AC96)&#xff0c;提供了保密性、完整性和認證的基礎。 密碼系統 假設Alice想給她的銀行發一個消息以劃轉資金&#xff0c;并希望這個消息是保密的&…

deb php7 fileinfo,linux安裝php7.2擴展fileinfo

最簡便的方法是使用pecl安裝php的擴展&#xff0c;方便快捷&#xff0c;這里使用的是源碼編譯安裝php擴展項目 中上傳圖片遇到的問題&#xff1a;明顯是fileinfo不被支持&#xff0c;沒有安裝fileinfo。接下來開始安裝因為我的linux服務器里比較干凈&#xff0c;所以之前的php源…

Android之提示java.lang.RuntimeException: Parcel: unable to marshal value Image問題

1 問題 使用Intent攜帶數據(putExtra)跳轉activity,提示如下錯誤 04-18 22:42:49.664 16194 16194 E AndroidRuntime: Process: com.appsinnova.android.keepshare, PID: 16194 04-18 22:42:49.664 16194 16194 E AndroidRuntime: java.lang.RuntimeException: Parcel: unabl…

使用keepalived實現雙機熱備

2019獨角獸企業重金招聘Python工程師標準>>> 通常說的雙機熱備是指兩臺機器都在運行&#xff0c;但并不是兩臺機器都同時在提供服務。當提供服務的一臺出現故障的時候&#xff0c;另外一臺會馬上自動接管并且提供服務&#xff0c;而且切換的時間非常短。下面來以kee…

《看聊天記錄都學不會C語言?太菜了吧》(8)牛郎和織女竟有一個孩子?

若是大一學子或者是真心想學習剛入門的小伙伴可以私聊我&#xff0c;若你是真心學習可以送你書籍&#xff0c;指導你學習&#xff0c;給予你目標方向的學習路線&#xff0c;無套路&#xff0c;博客為證。 本系列文章將會以通俗易懂的對話方式進行教學&#xff0c;對話中將涵蓋…

技術貼:觸摸屏(TP)技術交流

轉載自&#xff1a;易觸網科技 電容式TP的動作原理 PS:電容式TP動作原理是利用人體電流感應來進行的&#xff0c;當人的手指觸摸在TP上&#xff0c;與Panle上的ito電路形成一個耦合電容&#xff08;電容效應&#xff09;&#xff0c;於是手指從觸控點上吸走了一個微小的電流&am…

【遙感物候】植被物候與氣候(氣溫和降水)條件的空間相關性分析

植被生長與氣候的關系最為密切,通過計算植被各個生長季參數和氣溫、降水之間的相關系數可以分析生長季參數的變化與氣溫、降水之間的關系的程度。本文計算30年的植被物候參數和氣候數據之間的相關性,最終效果如下: 目錄 一、相關性分析原理

HttpContext.TraceIdentifier那嚴謹的設計

前言Asp.Net Core中有一個不受人重視的屬性HttpContext.TraceIdentifier&#xff0c;它在鏈路追蹤中非常有用&#xff0c;下面是官方的定義:在項目中一般會將該字段輸出到每一條日志中&#xff0c;也可以將此Id作為通用響應字段返回前端&#xff0c;后續可以根據該屬性和日志匹…

iOS - 富文本AttributedString

最近項目中用到了圖文混排&#xff0c;所以就研究了一下iOS中的富文本&#xff0c;打算把研究的結果分享一下&#xff0c;也是對自己學習的一個總結。 在iOS中或者Mac OS X中怎樣才能將一個字符串繪制到屏幕上呢&#xff1f; 簡單來說&#xff0c;是通過控件來完成的&#xff0…

php把數字倒著展示,jQuery+PHP實現動態數字展示特效

HTML本例假設要在頁面上動態展示(無需刷新整個頁面&#xff0c;只是局部刷新動態數字)當前在線用戶數&#xff0c;常見在一些統計平臺上應用。在HTML頁面中只需定義以下結構&#xff1a;代碼如下:當前在線&#xff1a;jQuery首先我們要定義一個動畫過程&#xff0c;使用jQuery的…

Android之實現多張圖片點擊預覽(支持放縮)和滑動

1 需求 多張圖片通過recycleView展示&#xff0c;然后點擊具體一張圖片支持預覽(支持放縮)和滑動 2 解決辦法 BaseRecyclerViewAdapterHelper com.github.chrisbanes.photoview.PhotoView ViewPage2 組合起來真香 https://github.com/CymChad/BaseRecyclerViewAdapt…

【Envi風暴】Envi5.4經典安裝圖文教程

ENVI(The Environment for Visualizing Images)是一個完整的遙感圖像處理平臺,應用匯集中的軟件處理技術覆蓋了圖像數據的輸入/輸出、圖像定標、圖像增強、糾正、正射校正、鑲嵌、數據融合以及各種變換、信息提取、圖像分類、基于知識的決策樹分類、與GIS的整合、DEM及地形信…

錯誤: nknown column 'xxxx' in 'where clause'

nknown column sdsds in where clause 運行環境&#xff1a;jdk1.7.0_17tomcat 7 spring&#xff1a;3.2.0 mybatis&#xff1a;3.2.7 eclipse 錯誤&#xff1a;nknown column sdsds in where clause 錯誤原因&#xff1a;數據庫查詢無用&#xff0c;可能很多寫sql語句都會遇到…

c/c++處理參數

直接上代碼&#xff1a;涉及函數getopt()&#xff0c;getopt_long() 1 #include <unistd.h>2 #include <stdlib.h>3 #include <stdio.h>4 #include <getopt.h>5 6 /*7 int main(int argc, char *argv[])8 {9 int opt; 10 char * optstring &q…

查缺補漏系統學習 EF Core 6 - 批量操作

推薦關注「碼俠江湖」加星標&#xff0c;時刻不忘江湖事這是 EF Core 系列的第七篇文章&#xff0c;上一篇文章講述了 EF Core 中的實體數據修改。這篇文章講一講 EF Core 如何進行批量操作。在眾多的 ORM 框架中&#xff0c;EF Core 的功能并不是最強大的那個&#xff0c;性能…

半小時一篇文過完C語言基礎知識點

若是大一學子或者是真心想學習剛入門的小伙伴可以私聊我&#xff0c;若你是真心學習可以送你書籍&#xff0c;指導你學習&#xff0c;給予你目標方向的學習路線&#xff0c;無套路&#xff0c;博客為證。 本文定位讀者為小白讀者&#xff0c;將使用最快的方法過完C語言基礎知識…