2009-06-12 9 views
41

Cuối cùng tôi đã gói đầu của tôi xung quanh IoC và DI trong C#, và đang đấu tranh với một số cạnh. Tôi đang sử dụng vùng chứa Unity, nhưng tôi nghĩ câu hỏi này áp dụng rộng rãi hơn.Làm thế nào để bạn hòa giải IDisposable và IoC?

Sử dụng vùng chứa IoC để phân phát các trường hợp triển khai IDisposable khiến tôi thất vọng! Làm thế nào bạn có nghĩa vụ phải biết nếu bạn nên vứt bỏ()? Ví dụ có thể đã được tạo ra chỉ dành cho bạn (và cho bạn nên Dispose() nó), hoặc nó có thể là một cá thể mà cuộc đời của bạn được quản lý ở nơi khác (và cho bạn tốt hơn không). Không có gì trong mã cho bạn biết, và thực tế điều này có thể thay đổi dựa trên cấu hình !!! Điều này có vẻ chết người với tôi.

Có thể bất kỳ chuyên gia IoC nào ở đó mô tả những cách tốt để xử lý sự mơ hồ này không?

+1

Giải pháp của tôi là sử dụng một IoC với quản lý đời phù hợp và tốt hệ thống hóa: AutoFac và Lâu đài Windsor có như vậy (mặc dù họ làm việc hơi khác nhau); Đơn vị 2.1 chỉ đơn giản là thất bại khi giao dịch với transients dưới các nhà quản lý suốt đời mặc định .. – user2864740

Trả lời

7

AutoFac xử lý việc này bằng cách cho phép tạo vùng chứa lồng nhau. Khi container được hoàn thành, nó sẽ tự động loại bỏ tất cả các đối tượng IDisposable bên trong nó. Thêm here.

.. Khi bạn giải quyết dịch vụ, Autofac theo dõi các thành phần dùng một lần (IDisposable) được giải quyết. Vào cuối đơn vị công việc, bạn vứt bỏ phạm vi thời gian liên quan và Autofac sẽ tự động dọn sạch/vứt bỏ các dịch vụ đã giải quyết.

+0

Rất thú vị. Tôi không biết bất cứ điều gì abaout AutoFac. Tôi không nghĩ sẽ quá khó để làm điều gì đó như thế này với Unity. Tôi sẽ phải suy nghĩ về điều này một chút. Cảm ơn! –

+1

Nó có thể là khó khăn để làm điều đó với Unity, kể từ khi xử lý xác định đã được nhúng vào kiến ​​trúc của Autofac kể từ đầu. –

+1

Castle Windsor là một container khác cho phép bạn làm điều đó, hoặc sử dụng sub-container, scoped lifestyle hoặc thông qua phát hành rõ ràng các thành phần của bạn bằng cách sử dụng phương thức 'container.Release' –

2

Tôi nghĩ rằng cách tiếp cận tốt nhất là chỉ đơn giản là không vứt bỏ thứ gì đó đã được tiêm; bạn phải giả định rằng các vòi phun đang làm việc phân bổ và deallocation.

+3

Điều này là rất bất tiện, vì tôi thường xuyên muốn tiêm các đối tượng dùng một lần. Nói chung, ngữ nghĩa của IDisposable yêu cầu người tạo/chủ sở hữu của đối tượng Vứt bỏ nó vào đúng thời điểm. Nếu vùng chứa chịu trách nhiệm, thì người dùng của cá thể cần phải thông báo cho vùng chứa khi nó được thực hiện. Điều này rất dễ quên. –

+1

Bị bỏ phiếu, vì phần sau của câu trả lời của bạn là xấu. Hầu hết các thùng chứa đều có cách để xử lý điều này, hoặc là các thùng chứa lồng nhau hoặc một cái gì đó giống như phương thức 'ReleaseAndDisposeAllHttpScopedObjects' của StructureMaps. Bạn vẫn cần phải tìm ra cách xử lý có thể được xử lý đúng cách ... trừ khi bạn muốn chạy ra khỏi các kết nối và như vậy. – Andy

2

Điều này phụ thuộc vào khung DI. Một số khung công tác cho phép bạn chỉ định xem bạn có muốn một cá thể được chia sẻ (luôn sử dụng cùng một tham chiếu) cho mỗi sự phụ thuộc được tiêm vào hay không. Trong trường hợp này, bạn rất có thể không muốn vứt bỏ.

Nếu bạn có thể chỉ định rằng bạn muốn một cá thể duy nhất được tiêm, thì bạn sẽ muốn vứt bỏ (vì nó đã được xây dựng cho riêng bạn). Tuy nhiên, tôi không quen thuộc với Unity - bạn phải kiểm tra các tài liệu về cách làm công việc này ở đó. Đó là một phần của thuộc tính với MEF và một số người khác mà tôi đã thử.

17

Bạn chắc chắn không muốn gọi Dispose() trên một đối tượng đã được đưa vào lớp học của bạn. Bạn không thể đưa ra giả định rằng bạn là người tiêu dùng duy nhất. Tốt nhất là để bọc đối tượng không được quản lý của bạn trong một số giao diện quản lý:

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

Đó chỉ là một ví dụ, tôi sẽ sử dụng File.ReadAllText (đường dẫn) nếu tôi đã cố gắng để đọc một tập tin văn bản vào một chuỗi.

cách tiếp cận khác là tiêm một nhà máy và quản lý các đối tượng bản thân:

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

Nhưng tôi muốn tiêm các đối tượng IDisposable, và hơn nữa, tôi có thể hoặc có thể không cần phải vứt bỏ() của chúng tại chỗ tiêm, tùy thuộc vào cấu hình. Ví dụ đầu tiên của bạn hoạt động khi tài nguyên cơ bản được truy cập một cách thoáng qua, nhưng không giúp đỡ nếu tài nguyên bền vững hơn. Trong ví dụ thứ hai, có vẻ lạ khi tôi tiêm một nhà máy theo cách này; đó là một trong những chức năng chính của mô hình IoC ngay từ đầu. Nếu tôi áp dụng IoC cho khái niệm này, dường như tôi sẽ trả lời câu hỏi gốc. Tôi nghĩ rằng các container chính nó cần phải tham gia này, một la AutoFac. –

+8

@Mr. Putty - dependency injection không loại bỏ sự cần thiết cho các nhà máy, nó chỉ làm cho một (ab) sử dụng chúng không cần thiết. Ví dụ: khi bạn không biết loại phụ thuộc cụ thể cho đến khi chạy hoặc cho các phụ thuộc có điều kiện đắt tiền (các đối tượng mà bạn thậm chí không cần nhiều tài nguyên để tạo), bạn có thể muốn chèn một nhà máy thay vì chính đối tượng đó. Tùy thuộc vào sự hỗ trợ của DI framework cho IDisposable, việc phun thuốc có thể là cách tốt nhất - nó chắc chắn minh bạch hơn nhiều lựa chọn thay thế. (+1) –

+0

Tôi nghĩ đây là lời khuyên tốt. Không bao giờ tiêm trực tiếp IDisposables; sử dụng trình bao bọc được quản lý thay thế - hoặc, sử dụng thời gian sống cho phép xử lý rõ ràng trong các trường hợp cụ thể như bối cảnh db, như HttpContextScoped trong StructureMap hoặc PerRequestLifetimeManager trong Unity. –

1

Trong khuôn khổ Unity, có hai cách để đăng ký lớp học tiêm: như độc thân (bạn nhận được luôn luôn cùng một ví dụ của khi bạn giải quyết nó), hoặc chẳng hạn như bạn nhận được một cá thể mới của lớp trên mỗi độ phân giải.

Trong trường hợp sau, bạn có trách nhiệm xử lý trường hợp đã giải quyết khi bạn không cần nó (đó là một cách tiếp cận khá hợp lý). Mặt khác, khi bạn vứt bỏ thùng chứa (lớp xử lý các độ phân giải đối tượng), tất cả các đối tượng singleton cũng sẽ tự động được xử lý.

Do đó, dường như không có vấn đề gì với các đối tượng dùng một lần được tiêm với khung Unity.Tôi không biết về các khung công tác khác, nhưng tôi cho rằng miễn là khuôn khổ tiêm phụ thuộc đủ vững chắc, nó chắc chắn xử lý vấn đề này theo cách này hay cách khác.

+1

Bài viết tuyệt vời về điều này: http://www.ladislavmrnka.com/2011/03/unity-build-in-lifetime-managers/ – TrueWill

+2

Đó là một cách tiếp cận * khủng khiếp *, không có gì "hợp lý" về việc đã tiêm và không được trả lời -objects cần phải được xử lý. Điều này là phức tạp vì nó là không thể cho các thành phần để tự loại bỏ phụ thuộc tiêm của họ bởi vì cuộc đời là không rõ - singleton, thoáng qua, vv Unity (2.x) phẳng ra không thành công trong vòng đời tiêu chuẩn không bao giờ được xử lý bởi container . – user2864740

+0

"Trong trường hợp sau ..." Khách hàng không biết thời tiết đó là trường hợp đầu tiên hoặc thứ hai, và không nên biết. Nó không nên biết về tuổi thọ được chọn của đối tượng được tiêm. Tôi không đồng ý với nhận xét của user2864740 rằng Unity thất bại. Tôi nghĩ rằng đó là một đặc điểm khuôn khổ mà chúng ta phải đối phó, thật không may. –

2

Đặt mặt tiền ở phía trước vùng chứa cũng có thể giải quyết vấn đề này. Ngoài ra, bạn có thể mở rộng nó để theo dõi chu kỳ cuộc sống phong phú hơn như tắt dịch vụ và khởi động hoặc chuyển đổi trạng thái ServiceHost.

Vùng chứa của tôi có xu hướng sống trong IExtension triển khai giao diện IServiceLocator. Nó là một mặt tiền cho sự thống nhất, và cho phép dễ dàng truy cập vào các dịch vụ WCF. Ngoài ra, tôi có quyền truy cập vào các sự kiện dịch vụ từ ServiceHostBase.

Mã bạn kết thúc sẽ cố gắng xem có bất kỳ đăng ký singleton hoặc bất kỳ loại nào được tạo ra thực hiện bất kỳ giao diện nào mà mặt tiền theo dõi hay không.

Vẫn không cho phép xử lý kịp thời khi bạn bị ràng buộc với các sự kiện này nhưng nó giúp ích một chút.

Nếu bạn muốn vứt bỏ một cách kịp thời (aka, bây giờ v.s. khi tắt dịch vụ). Bạn cần phải biết rằng mục bạn nhận được là dùng một lần, nó là một phần của logic kinh doanh để vứt bỏ nó, vì vậy IDisposable nên là một phần của giao diện của đối tượng. Và có lẽ cần phải xác minh các kỳ vọng không liên quan đến phương pháp vứt bỏ được gọi.

+0

Bạn không mất lợi thế của việc xây dựng tiêm theo cách này? –

4

Điều này đã khiến tôi băn khoăn thường xuyên. Mặc dù không hài lòng về nó, tôi luôn luôn đi đến kết luận rằng không bao giờ trở về một đối tượng IDisposable một cách thoáng qua là tốt nhất.

Gần đây, tôi đã lặp lại câu hỏi cho chính mình: Đây có phải là vấn đề của IoC hay vấn đề về khung .net? Xử lý là khó xử anyway. Nó không có mục đích chức năng có ý nghĩa, chỉ mang tính kỹ thuật. Vì vậy, nó là một vấn đề khuôn khổ mà chúng ta phải đối phó, hơn là một vấn đề IoC.

Điều tôi thích về DI là tôi có thể yêu cầu hợp đồng cung cấp chức năng cho tôi mà không phải lo lắng về chi tiết kỹ thuật. Tôi không phải là chủ sở hữu. Không có kiến ​​thức về lớp mà nó ở. Không có kiến ​​thức về những công nghệ được yêu cầu để thực hiện hợp đồng, không phải lo lắng về cuộc đời. Mã của tôi trông đẹp và sạch sẽ, và có khả năng kiểm tra cao. Tôi có thể thực hiện trách nhiệm trong các lớp mà họ thuộc về.

Vì vậy, nếu có ngoại lệ đối với quy tắc này yêu cầu tôi phải tổ chức toàn bộ thời gian, hãy tạo ngoại lệ đó. Cho dù tôi có thích hay không. Nếu đối tượng thực hiện giao diện yêu cầu tôi phải vứt bỏ nó, tôi muốn biết về nó kể từ đó tôi được kích hoạt để sử dụng đối tượng càng ngắn càng tốt. Một thủ thuật bằng cách giải quyết nó bằng cách sử dụng một thùng chứa trẻ em được xử lý một thời gian sau đó có thể vẫn khiến tôi giữ vật thể sống lâu hơn tôi nên. Thời gian cho phép của đối tượng được xác định khi đăng ký đối tượng. Không phải bởi các chức năng tạo ra một container con và giữ cho rằng trong một thời gian nhất định.

Vì vậy, miễn là chúng tôi phát triển cần phải lo lắng về việc xử lý (sẽ thay đổi bao giờ?) Tôi sẽ cố gắng tiêm càng ít đối tượng dùng một lần càng tốt. 1. Tôi cố gắng để làm cho đối tượng không IDisposable, ví dụ bằng cách không giữ các đối tượng dùng một lần trên cấp lớp, nhưng trong một phạm vi nhỏ hơn. 2. Tôi cố gắng làm cho đối tượng có thể tái sử dụng để có thể áp dụng một trình quản lý lâu dài khác.

Nếu điều này là không khả thi, tôi sử dụng một nhà máy để cho biết rằng người dùng của hợp đồng được tiêm là chủ sở hữu và phải chịu trách nhiệm về nó.

Có một cảnh báo: thay đổi người triển khai hợp đồng từ không dùng một lần sang dùng một lần sẽ là một thay đổi đột phá. Vào thời điểm đó giao diện sẽ không còn được đăng ký, nhưng giao diện cho nhà máy.Nhưng tôi nghĩ điều này cũng áp dụng cho các kịch bản khác. Việc quên sử dụng một thùng chứa con sẽ từ thời điểm đó khi đưa ra các vấn đề về bộ nhớ. Cách tiếp cận nhà máy sẽ gây ra một IoC giải quyết ngoại lệ.

Một số mã ví dụ:

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 
}