2012-05-09 37 views
29

Bắt đầu từ tình hình follwing:Khai báo IDisposable cho lớp hoặc giao diện?

public interface ISample 
{ 
} 

public class SampleA : ISample 
{ 
    // has some (unmanaged) resources that needs to be disposed 
} 

public class SampleB : ISample 
{ 
    // has no resources that needs to be disposed 
} 

Các SampleA lớp nên thực hiện IDisposable giao diện cho giải phóng tài nguyên. Bạn có thể giải quyết việc này theo hai cách:

1. Thêm giao diện cần thiết cho SampleA lớp:

public class SampleA : ISample, IDisposable 
{ 
    // has some (unmanaged) resources that needs to be disposed 
} 

2. Thêm nó vào ISample giao diện và lực lượng có nguồn gốc lớp học để thực hiện nó:

public interface ISample : IDisposable 
{ 
} 

Nếu bạn đặt nó vào giao diện, bạn buộc phải triển khai thực hiện IDisposable ngay cả khi chúng không có gì để vứt bỏ. Mặt khác, nó là rất rõ ràng để thấy rằng việc thực hiện cụ thể của một giao diện đòi hỏi một vứt bỏ/sử dụng khối và bạn không cần phải cast như IDisposable để làm sạch. Có thể có một số ưu/nhược điểm khác trong cả hai cách ... tại sao bạn đề nghị sử dụng một cách ưu tiên cho cách khác?

+4

Có khả năng mã được viết dựa trên giao diện (có lẽ là được giao cho một cá thể đã được xây dựng) có trách nhiệm * kết thúc * thời gian hữu ích (hữu ích) của cá thể đó? –

+1

@Damien_The_Unbeliever: Ok, giả sử ISample xuất phát từ kết quả của phương thức factory hoặc thông qua Dependency Injection. – Beachwalker

+2

Đó là điều - nếu nó * là * đến từ một nhà máy, thì có khả năng mã của bạn * là * chịu trách nhiệm xử lý - vì vậy tôi sẽ đặt nó trên Giao diện. Nhưng nếu nó được tiêm thì tôi giả định rằng vòi phun chịu trách nhiệm cho cả đời, vì vậy nó sẽ không * phù hợp với giao diện - Tôi không nghĩ rằng có một câu trả lời một kích thước phù hợp cho câu hỏi. –

Trả lời

6

Nếu bạn áp dụng các mô hình using(){} cho tất cả các giao diện của bạn nó là tốt nhất để có ISample xuất phát từ IDisposable vì các nguyên tắc nhỏ khi thiết kế giao diện là để ủng hộ "dễ sử dụng" qua "tính dễ thực hiện ".

+0

Bạn sẽ sử dụng câu lệnh bằng cách sử dụng trong foreach như thế nào? –

+1

"... và bạn chỉ thấy giao diện ..." Đó là chìa khóa. Nhiều giải pháp tốt, OOP sẽ chỉ sử dụng giao diện, vì vậy tôi nghĩ rằng nó có ý nghĩa cho giao diện để thực hiện IDisposable. Bằng cách đó, mã tiêu thụ có thể xử lý tất cả các lớp con theo cùng một cách. –

6

Cá nhân, nếu tất cả ISample nên được dùng một lần, tôi sẽ đặt nó trên giao diện, nếu chỉ có một số tôi chỉ đặt nó trên các lớp mà nó cần được.

Có vẻ như bạn có trường hợp sau.

+0

Nhưng làm thế nào để đảm bảo việc gọi Dispose() nếu người sử dụng lib của bạn không biết nó và có thể có một thực hiện với là cần thiết để xử lý? – Beachwalker

+0

@Stegi - Dễ dàng. Chỉ cần kiểm tra 'là IDisposable' và gọi' Dispose' nếu có. – Jamiec

+0

Có, tôi biết, nhưng điều này sẽ rác mã ... bởi vì MỌI thực hiện khác (ví dụ như IList, tôi ...) có thể được IDisposable. Dường như với tôi như IDisposable nên là một đối tượng cơ sở chung lớp thực hiện (ngay cả khi có sản phẩm nào). – Beachwalker

0

Cá nhân tôi sẽ chọn 1, trừ khi bạn tạo ví dụ cụ thể cho hai. Một ví dụ điển hình về hai là IList.

An IList có nghĩa là bạn cần triển khai chỉ mục cho bộ sưu tập của mình. Tuy nhiên, một số IList thực sự cũng có nghĩa là bạn là IEnumerable và bạn cần có GetEnumerator() cho lớp học của mình.

Trong trường hợp bạn đang do dự rằng các lớp thực hiện ISample sẽ cần phải triển khai IDisposable, nếu không phải mọi lớp triển khai giao diện của bạn phải triển khai IDisposable thì không bắt buộc chúng.

Tập trung vào IDispoable cụ thể, IDispoable trong các lực lượng lập trình viên cụ thể sử dụng lớp học của bạn để viết một số mã xấu xí hợp lý. Ví dụ,

foreach(item in IEnumerable<ISample> items) 
{ 
    try 
    { 
     // Do stuff with item 
    } 
    finally 
    { 
     IDisposable amIDisposable = item as IDisposable; 
     if(amIDisposable != null) 
      amIDisposable.Dispose(); 
    } 
} 

Không chỉ là mã khủng khiếp, sẽ có một hình phạt hiệu quả đáng kể trong việc đảm bảo có một khối finally để vứt bỏ mục cho mỗi lần lặp của danh sách đó, ngay cả khi Dispose() chỉ trả về trong thực hiện.

Dán mã để trả lời một trong các nhận xét ở đây, dễ đọc hơn.

+0

Nhưng nếu có các nhà máy (sử dụng quặng đơn giản của IoC-Container) cung cấp các đối tượng triển khai có hoặc không có yêu cầu xử lý chúng? Bạn sẽ phải gọi Dispose() bằng cách truyền và gọi nó một cách rõ ràng. Bằng cách này mã của bạn cần kiến ​​thức về việc thực hiện (có thể) ... một số loại khớp nối? Nếu không, bạn chỉ có thể sử dụng một khối sử dụng rất dễ dàng. – Beachwalker

+0

@Stegi sử dụng khối có thể không trông xấu xí, nhưng nó vẫn sẽ có hình phạt hiệu suất. Ngoài ra tôi không thấy làm thế nào bạn sẽ sử dụng một khối sử dụng bên trong của một foreach ví dụ.Mã của Microsoft được thắp sáng với các ví dụ mà nó thực hiện điều này, IDisposable amIDisposable = object là IDisposable; if (amIDisposable! = null) amIDisposable.Dispose(); Bởi vì như không ném một ngoại lệ nếu nó không thể đưa nó vào một IDisposable, hình phạt hiệu suất là gần như không tồn tại. –

+0

Thậm chí nếu chỉ 1% các lớp thực hiện hoặc kế thừa từ một số loại cụ thể sẽ làm bất cứ điều gì trong 'IDisposable.Dispose', nếu nó sẽ cần thiết để gọi' IDisposable.Dispose' trên một biến hoặc trường được khai báo là kiểu đó (trái ngược với một trong một kiểu thực thi hoặc có nguồn gốc), đó là một dấu hiệu tốt cho thấy chính bản thân kiểu đó sẽ kế thừa hoặc thực hiện 'IDisposable'. Thử nghiệm trong thời gian chạy cho dù một cá thể đối tượng có triển khai 'IDisposable' và gọi' IDisposable.Dispose' trên nó nếu như vậy (như là cần thiết với non-generic 'IEnumerable') là một mùi mã chính. – supercat

2

Có thể là một giao diện rất phổ biến, không có hại gì khi giao diện của bạn kế thừa từ giao diện đó. Bạn sẽ tránh việc kiểm tra loại trong mã của bạn với chi phí duy nhất để thực hiện no-op trong một số triển khai ISample của bạn. Vì vậy, lựa chọn thứ 2 của bạn có thể tốt hơn từ quan điểm này.

+0

Điều đó không đúng. Nếu lớp của bạn có một trường thực hiện IDisposable thì lớp đó phải là IDisposable để gọi Dispose của trường. Nếu bạn thêm IDisposable khi nó không cần thiết, bạn kết thúc với một nửa các lớp của bạn đang được IDisposable. –

3

Một giao diện IFoo lẽ nên thực hiện IDisposable nếu có khả năng là ít nhất một số một số hiện thực sẽ thực hiện IDisposable, và trên ít nhất một số trường hợp các tài liệu tham khảo còn sống sót cuối cùng để một thể hiện sẽ được lưu trữ trong một biến hoặc lĩnh vực loại IFoo. Nó hầu như chắc chắn sẽ thực hiện IDisposable nếu bất kỳ triển khai nào có thể triển khai IDisposable và các phiên bản sẽ được tạo thông qua giao diện nhà máy (như trường hợp với các trường hợp IEnumerator<T>, trong nhiều trường hợp được tạo thông qua giao diện nhà máy IEnumerable<T>).

So sánh IEnumerable<T>IEnumerator<T> là hướng dẫn. Một số loại thực hiện IEnumerable<T> cũng thực hiện IDisposable, nhưng mã tạo ra các cá thể loại như vậy sẽ biết chúng là gì, biết rằng chúng cần xử lý và sử dụng chúng làm loại cụ thể của chúng. Các trường hợp như vậy có thể được chuyển cho các thường trình khác như loại IEnumerable<T> và các thường trình khác sẽ không có manh mối rằng các đối tượng cuối cùng sẽ cần xử lý, nhưng các thường trình khác trong hầu hết các trường hợp không phải là các đối tượng cuối cùng giữ tham chiếu đến đối tượng. Ngược lại, các trường hợp của IEnumerator<T> thường được tạo, sử dụng và cuối cùng bị bỏ qua, theo mã không biết gì về các loại cơ bản của các trường hợp đó ngoài thực tế là chúng được trả về bởi IEnumerable<T>. Một số triển khai của IEnumerable<T>.GetEnumerator() triển khai trở lại của IEnumerator<T> sẽ bị rò rỉ nguồn lực nếu phương pháp IDisposable.Dispose của họ không được gọi trước khi họ bị bỏ rơi, và hầu hết các mã mà chấp nhận tham số kiểu IEnumerable<T> sẽ không có cách nào để biết nếu loại này có thể được truyền cho nó. Mặc dù nó có thể cho IEnumerable<T> để bao gồm một thuộc tính EnumeratorTypeNeedsDisposal để cho biết liệu trả lại IEnumerator<T> sẽ phải được xử lý hoặc đơn giản yêu cầu các thói quen gọi GetEnumerator() kiểm tra loại đối tượng được trả về để xem nó có thực hiện IDisposable hay không. để vô điều kiện gọi phương thức Dispose mà có thể không làm bất cứ điều gì, hơn là xác định xem Dispose là cần thiết và chỉ gọi nó nếu có.

+0

'IEnumerator ' là một ví dụ hay về một giao diện mà quản lý lâu dài chắc chắn là một phần của API của nó. Điều này có thể được thực hiện để giữ cho mọi thứ đơn giản hơn. Tuy nhiên, nó có thể được hình thành rằng người ta sẽ muốn gọi 'GetEnumerator()' trong một phương pháp và tại một số điểm chuyển điều tra sang một phương pháp khác. Phương pháp khác lý tưởng không có quyền truy cập vào 'Dispose()'. Nếu khuôn khổ có 'giao diện IDisposableEnumerator : IEnumerator , IDisposable {}' và 'giao diện IEnumerator : IEnumerator {T hiện tại {get; }} ', điều này có thể làm cho một số thứ" sạch hơn "(nhưng phức tạp hơn nữa: - /). – binki

+1

@binki: Điều gì sẽ làm cho mọi thứ trở nên rõ ràng hơn cho 'IEnumerator' để triển khai' IDisposable'. Mã mà nhận được một 'IEnumerable' nó không biết gì về phải đoán rằng nó có thể là cần thiết để gọi 'Dispose' trên một' IEnumerator' nó trả về. Nếu nó chuyển số đếm ngược được trả về cho một số phương thức khác để tiêu thụ sau này, thì phương thức thứ hai có thể có trách nhiệm gọi là 'Vứt bỏ' trên nó. Kể từ khi gọi một lệnh 'Dispose' trên một cái gì đó được gọi là' IDisposable' nhanh hơn việc kiểm tra xem một đối tượng có thực hiện 'IDisposable' ... – supercat

+1

... cách đơn giản nhất cho các đối tượng để thực hiện nghĩa vụ của họ hay không 'Vứt bỏ 'trên các điều tra viên mà họ sở hữu trước khi từ bỏ chúng, mà không quan tâm liệu những điều tra viên đó có quan tâm hay không. – supercat

16

Tiếp theo Inteface Segregation Principle của SOLID nếu bạn thêm IDisposable vào giao diện bạn đang đưa ra phương pháp cho khách hàng mà không phải quan tâm đến việc vì vậy bạn nên thêm nó vào A.

Bên cạnh đó, giao diện là không bao giờ dùng một lần vì disposability là một cái gì đó liên quan đến việc thực hiện cụ thể của giao diện, không bao giờ với giao diện chính nó.

Bất kỳ giao diện nào cũng có thể được triển khai có hoặc không có phần tử cần được xử lý.

1

Tôi bắt đầu nghĩ rằng việc đặt IDisposable trên giao diện có thể gây ra một số sự cố. Nó ngụ ý rằng tuổi thọ của tất cả các đối tượng thực hiện giao diện đó có thể được kết thúc một cách đồng bộ một cách an toàn. Ví dụ, nó cho phép bất cứ ai để viết mã như thế này và đòi hỏi tất cả triển khai để hỗ trợ IDisposable:

using (ISample myInstance = GetISampleInstance()) 
{ 
    myInstance.DoSomething(); 
} 

Chỉ mã mà được truy cập vào các loại bê tông có thể biết đúng cách để kiểm soát thời gian tồn tại của đối tượng.Ví dụ: một loại có thể không cần xử lý ở nơi đầu tiên, nó có thể hỗ trợ IDisposable hoặc có thể yêu cầu awaiting quá trình dọn dẹp không đồng bộ sau khi bạn sử dụng xong (ví dụ: something like option 2 here).

Tác giả giao diện không thể dự đoán tất cả các nhu cầu quản lý tuổi thọ/phạm vi có thể có trong tương lai của việc triển khai lớp học. Mục đích của một giao diện là cho phép một đối tượng để lộ một số API để nó có thể được làm cho hữu ích cho một số người tiêu dùng. Một số giao diện có thể có liên quan đến quản lý lâu dài (chẳng hạn như IDisposable chính nó), nhưng trộn chúng với giao diện không liên quan đến quản lý lâu dài có thể làm cho việc thực hiện giao diện khó khăn hoặc không thể. Nếu bạn có rất ít triển khai giao diện và cấu trúc mã của bạn để người tiêu dùng và người quản lý phạm vi/đời sống của giao diện giống nhau, thì sự khác biệt này không rõ ràng lúc đầu. Nhưng nếu bạn bắt đầu truyền đối tượng của bạn xung quanh, điều này sẽ rõ ràng hơn.

void ConsumeSample(ISample sample) 
{ 
    // INCORRECT CODE! 
    // It is a developer mistake to write “using” in consumer code. 
    // “using” should only be used by the code that is managing the lifetime. 
    using (sample) 
    { 
     sample.DoSomething(); 
    } 

    // CORRECT CODE 
    sample.DoSomething(); 
} 

async Task ManageObjectLifetimesAsync() 
{ 
    SampleB sampleB = new SampleB(); 
    using (SampleA sampleA = new SampleA()) 
    { 
     DoSomething(sampleA); 
     DoSomething(sampleB); 
     DoSomething(sampleA); 
    } 

    DoSomething(sampleB); 

    // In the future you may have an implementation of ISample 
    // which requires a completely different type of lifetime 
    // management than IDisposable: 
    SampleC = new SampleC(); 
    DoSomething(sampleC); 
    sampleC.Complete(); 
    await sampleC.Completion; 
} 

class SampleC : ISample 
{ 
    public void Complete(); 
    public Task Completion { get; } 
} 

Trong ví dụ trên, tôi đã trình bày ba loại kịch bản quản lý lâu dài, bổ sung vào hai trường hợp bạn cung cấp.

  1. SampleAIDisposable với sự hỗ trợ đồng bộ using() {}.
  2. SampleB sử dụng bộ sưu tập rác nguyên chất (nó không tiêu thụ bất kỳ tài nguyên nào).
  3. SampleC sử dụng các tài nguyên ngăn không cho nó bị xử lý đồng bộ và yêu cầu await vào cuối vòng đời của nó (để nó có thể thông báo mã quản lý lâu dài được sử dụng tài nguyên và bong bóng lên bất kỳ trường hợp ngoại lệ nào không đồng bộ).

Bằng cách giữ quản lý đời tách biệt với giao diện khác, bạn có thể ngăn chặn sai lầm phát triển (ví dụ, các cuộc gọi ngẫu nhiên để Dispose()) và sạch hơn hỗ trợ mô hình quản lý đời/phạm vi không lường trước được trong tương lai.

+0

Các loại không yêu cầu dọn dẹp có thể thực thi 'IDisposable' dễ dàng và chính xác thông qua' void IDisposable.Dispose() {}; '. Nếu một số đối tượng được trả về từ chức năng của nhà máy sẽ cần dọn dẹp, thì việc trả về một loại thực hiện 'IDisposable' dễ dàng và đơn giản hơn và yêu cầu khách hàng cân bằng mọi cuộc gọi đến chức năng của nhà máy với một lệnh gọi đến' Dispose' của đối tượng trả về yêu cầu khách hàng xác định đối tượng nào cần dọn dẹp. – supercat

Các vấn đề liên quan