2009-06-12 40 views
41

我終於在C#中圍繞IoC和DI進行了包圍,並且在一些邊緣處掙扎着。我正在使用Unity容器,但我認爲這個問題更廣泛地適用。你如何協調IDisposable和IoC?

使用IoC容器來分配實現IDisposable的實例會讓我大吃一驚!你應該怎麼知道你是否應該Dispose()?實例可能是爲你創建的(因此你應該Dispose()它),或者它可能是一個實例,它的生命週期在其他地方被管理(因此你最好不要)。代碼中沒有任何內容告訴你,實際上這可能會根據配置而改變!這對我來說似乎是致命的。

那裏的任何IoC專家是否可以描述處理這種歧義的好方法?

+1

我的解決辦法是使用一個IoC和適當和公編纂壽命管理:AutoFac和城堡溫莎有這樣的(儘管它們的工作方式略有不同);在處理默認終身管理器下的瞬變時,2.1單元根本失敗。 – user2864740 2015-03-03 21:00:06

回答

7

AutoFac通過允許創建一個嵌套容器來處理這個問題。當容器完成時,它會自動處理其中的所有IDisposable對象。更多here

..在解決服務問題時,Autofac會跟蹤已解決的一次性(IDisposable)組件。 在工作單元結束時,您將處理關聯的生命週期範圍,Autofac將自動清理/處理已解決的服務。

+0

非常有趣。我什麼都不知道AutoFac。我認爲用Unity做這樣的事情不會太難。我將不得不考慮這一點。謝謝! – 2009-06-13 07:07:30

+1

Unity可能很難做到這一點,因爲確定性處置自一開始就已經嵌入到Autofac架構中。 – 2009-06-13 17:56:28

+1

Castle Windsor是另一個容器,可以使用子容器,範圍生活方式或通過使用`container.Release`方法明確釋放組件來實現這個功能。 – 2010-05-18 11:36:07

2

我認爲一般來說最好的方法是簡單地不處理已注入的東西;您必須假定噴油器正在執行分配和重新分配。

+3

這是非常不方便的,因爲我經常要注入一次性物體。通常,IDisposable的語義要求對象的創建者/擁有者在正確的時間處理它。如果容器負責,那麼實例的用戶需要告訴容器何時完成。這很容易忘記。 – 2009-06-13 07:05:36

+1

Downvoted,因爲你答案的後半部分是不好的。大多數容器都有處理這個問題的方法,可以是嵌套容器,也可以是StructureMaps`ReleaseAndDisposeAllHttpScopedObjects`方法。你仍然需要弄清楚一次性物品如何妥善處置......除非你喜歡用盡連接等。 – Andy 2012-07-16 18:00:20

2

這取決於DI框架。有些框架允許您指定是否需要爲注入的每個依賴項創建共享實例(始終使用相同的引用)。在這種情況下,你很可能不想處置。

如果你可以指定你想要一個唯一的實例注入,那麼你會想要處置(因爲它是專門爲你構建的)。儘管我對Unity並不熟悉,但您必須查看文檔以瞭解如何在此工作。這是MEF和我嘗試過的其他一些屬性的一部分。

17

你絕對不想在注入到你的類中的對象上調用Dispose()。你不能假設你是唯一的消費者。最好的辦法是來包裝你的非託管對象在某些管理界面:

public class ManagedFileReader : IManagedFileReader 
{ 
    public string Read(string path) 
    { 
     using (StreamReader reader = File.OpenRead(path)) 
     { 
      return reader.ReadToEnd(); 
     } 
    } 
} 

這只是一個例子,我會用File.ReadAllText(路徑)如果我想讀的文本文件轉換成字符串。

另一種方法是注入一個工廠和管理自己的對象:

public void DoSomething() 
{ 
    using (var resourceThatShouldBeDisposed = injectedFactory.CreateResource()) 
    { 
     // do something 
    } 
} 
1

在統一框架下,有兩種方法來註冊注入類:爲單身(你永遠是同一個實例當你解決它時),或者你在每個分辨率上得到一個新類的實例。

在後一種情況下,您有責任在您不需要它的時候處理已解析的實例(這是一種非常合理的方法)。另一方面,當您處理容器(處理對象分辨率的類)時,所有單例對象也會自動處理。

因此,使用Unity框架注入一次性對象顯然沒有問題。我不知道其他框架,但我認爲只要依賴注入框架足夠穩固,它肯定會以某種方式處理這個問題。

2

將正面放在容器的前面也可以解決此問題。另外,您可以擴展它來跟蹤更加豐富的生命週期,如服務關閉和啓動或ServiceHost狀態轉換。

我的容器傾向於生活在實現IServiceLocator接口的IExtension中。這是一個統一的門面,並允許在WCf服務中輕鬆訪問。另外我可以訪問ServiceHostBase中的服務事件。

您最終得到的代碼將嘗試查看是否有已註冊的單例或創建的任何類型實現了Facade跟蹤的任何接口。

仍然不允許及時處置,因爲你被綁定到這些事件,但它有點幫助。

如果您希望及時處置(也就是現在的服務關閉v.s.)。你需要知道你得到的物品是一次性的,它是處理它的業務邏輯的一部分,所以IDisposable應該是物體接口的一部分。並且可能應該驗證與處置方法相關的未初始化的期望。

4

這也讓我感到困惑。雖然對此並不滿意,但我總是得出結論:永遠不會以暫時的方式返回一個IDisposable對象是最好的。

最近,我爲自己解釋了這個問題:這真的是IoC問題還是.net框架問題?無論如何,處置都很尷尬。它沒有意義的功能目的,只有技術。所以這是一個我們不得不處理的框架問題,而不是IoC問題。

我喜歡DI的一點是,我可以要求提供我的功能的合同,而不必打擾技術細節。我不是主人。沒有關於它在哪一層的知識。沒有關於完成合同需要哪些技術的知識,不用擔心一生。我的代碼看起來不錯,乾淨,而且是高度可測試的。我可以在他們所屬的層次上實施責任。

因此,如果這個規則有一個例外,它要求我組織生命期,那麼讓我們來做這個例外。我是否喜歡它。如果實現接口的對象需要我來處理它,我想知道它,因爲那時我會觸發儘可能短的對象。使用一段時間後放置的子容器解決它的一個詭計可能仍然會導致我保持對象的存活時間超過我應該的時間。註冊對象時確定對象的允許使用期限。不是通過創建子容器的功能並在一定時間內保持該容器。因此,只要我們的開發人員需要擔心處置(將會改變嗎?),我會嘗試儘可能少地注入瞬態的一次性對象。 1.我嘗試讓對象不是IDisposable,例如,不要在類級別保留一次性對象,而是在較小的範圍內。 2.我嘗試使對象可重用,以便可以應用不同的生命期管理器。

如果這是不可行的,我使用工廠來表明注入合同的用戶是所有者,並且應該對其負責。

有一個警告:將合同執行者從非一次性更改爲一次性將是一個突破性變化。那時界面將不再被註冊,而是到工廠的界面。但我認爲這也適用於其他場景。忘記使用子容器會從那時起給內存問題。工廠方法將導致IoC解決異常。

一些示例代碼:

using System; 
using Microsoft.Practices.Unity; 

namespace Test 
{ 
    // Unity configuration 
    public class ConfigurationExtension : UnityContainerExtension 
    { 
     protected override void Initialize() 
     { 
      // Container.RegisterType<IDataService, DataService>(); Use factory instead 
      Container.RegisterType<IInjectionFactory<IDataService>, InjectionFactory<IDataService, DataService>>(); 
     } 
    } 

    #region General utility layer 

    public interface IInjectionFactory<out T> 
     where T : class 
    { 
     T Create(); 
    } 

    public class InjectionFactory<T2, T1> : IInjectionFactory<T2> 
     where T1 : T2 
     where T2 : class 

    { 
     private readonly IUnityContainer _iocContainer; 

     public InjectionFactory(IUnityContainer iocContainer) 
     { 
      _iocContainer = iocContainer; 
     } 

     public T2 Create() 
     { 
      return _iocContainer.Resolve<T1>(); 
     } 
    } 

    #endregion 

    #region data layer 

    public class DataService : IDataService, IDisposable 
    { 
     public object LoadData() 
     { 
      return "Test data"; 
     } 

     protected virtual void Dispose(bool disposing) 
     { 
      if (disposing) 
      { 
       /* Dispose stuff */ 
      } 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 
    } 

    #endregion 

    #region domain layer 

    public interface IDataService 
    { 
     object LoadData(); 
    } 

    public class DomainService 
    { 
     private readonly IInjectionFactory<IDataService> _dataServiceFactory; 

     public DomainService(IInjectionFactory<IDataService> dataServiceFactory) 
     { 
      _dataServiceFactory = dataServiceFactory; 
     } 

     public object GetData() 
     { 
      var dataService = _dataServiceFactory.Create(); 
      try 
      { 
       return dataService.LoadData(); 
      } 
      finally 
      { 
       var disposableDataService = dataService as IDisposable; 
       if (disposableDataService != null) 
       { 
        disposableDataService.Dispose(); 
       } 
      } 
     } 
    } 

    #endregion 
}