2009-09-13 30 views
20

Tôi không nghĩ câu hỏi này đã được hỏi trước đây. Tôi là một chút bối rối về cách tốt nhất để thực hiện IDisposable trên một lớp kín - đặc biệt, một lớp niêm phong mà không kế thừa từ một lớp cơ sở. (Đó là, một "lớp học kín tinh khiết" là thuật ngữ của tôi.)Thực hiện IDisposable trên lớp kín

Có thể một số bạn đồng ý với tôi rằng hướng dẫn thực hiện IDisposable rất khó hiểu. Điều đó nói rằng, tôi muốn biết rằng cách tôi dự định thực hiện IDisposable là đủ và an toàn.

Tôi đang thực hiện một số mã P/Invoke phân bổ IntPtr đến Marshal.AllocHGlobal và tự nhiên, tôi muốn xóa sạch bộ nhớ không được quản lý mà tôi đã tạo. Vì vậy, tôi đang nghĩ đến một cái gì đó như thế này

using System.Runtime.InteropServices; 

[StructLayout(LayoutKind.Sequential)] 
public sealed class MemBlock : IDisposable 
{ 
    IntPtr ptr; 
    int length; 

    MemBlock(int size) 
    { 
      ptr = Marshal.AllocHGlobal(size); 
      length = size; 
    } 

    public void Dispose() 
    { 
      if (ptr != IntPtr.Zero) 
      { 
       Marshal.FreeHGlobal(ptr); 
       ptr = IntPtr.Zero; 
       GC.SuppressFinalize(this); 
      } 
    } 

    ~MemBlock() 
    { 
      Dispose(); 
    }  
} 

Tôi giả định rằng vì MemBlock là hoàn toàn kín và không bao giờ xuất phát từ một lớp khác mà thực hiện một virtual protected Dispose(bool disposing) là không cần thiết.

Ngoài ra, người hoàn tất có cần thiết không? Tất cả những suy nghĩ đều được chào đón.

Trả lời

13

Trình hoàn thiện là cần thiết làm cơ chế dự phòng để cuối cùng không có tài nguyên không được quản lý nếu bạn quên gọi Dispose.

Không, bạn không nên khai báo phương thức virtual trong lớp học sealed. Nó sẽ không biên dịch chút nào. Ngoài ra, bạn không nên khai báo các thành viên protected mới trong các lớp sealed mới.

+0

Nhưng tất nhiên trong trường hợp đó một lớp niêm phong bắt nguồn từ một lớp cơ sở thì việc vứt bỏ ảo sẽ là cần thiết - đúng không? – zebrabox

+0

Ngoài ra. Các finaliser có nghĩa là thêm vào một hàng đợi finalizer và có chi phí hiệu quả một thu gom rác đôi. Có vẻ như một hình phạt nặng nề phải trả cho việc sử dụng các tài nguyên không được quản lý. Là không có cách nào để tránh việc thực hiện hit? – zebrabox

+0

Trong trường hợp đó, bạn sẽ 'ghi đè' phương thức. Bạn không thể khai báo bất kỳ phương thức nào trong lớp 'sealed' là' virtual'. Đó là một trình biên dịch ** lỗi **. –

7

Một bổ sung nhỏ; trong chung trường hợp, một mô hình phổ biến là có một phương pháp Dispose(bool disposing), để bạn biết liệu bạn đang ở trong Dispose (nơi mà mọi thứ hơn có sẵn) vs finalizer (nơi bạn không nên thực sự chạm vào bất kỳ đối tượng quản lý kết nối khác) .

Ví dụ:

public void Dispose() { Dispose(true); } 
~MemBlock() { Dispose(false); } 
void Dispose(bool disposing) { // would be protected virtual if not sealed 
    if(disposing) { // only run this logic when Dispose is called 
     GC.SuppressFinalize(this); 
     // and anything else that touches managed objects 
    } 
    if (ptr != IntPtr.Zero) { 
      Marshal.FreeHGlobal(ptr); 
      ptr = IntPtr.Zero; 
    } 
} 
+0

Yep điểm tốt Marc nhưng nếu tôi biết rằng tôi chỉ xử lý tài nguyên không được quản lý thì điều đó có cần thiết không? – zebrabox

+0

Ngoài ra - câu hỏi rất ngu ngốc nhưng nếu mô hình Vứt bỏ là để xác định các tài nguyên không được quản lý thì tại sao tôi muốn xử lý các tài nguyên được quản lý dùng một lần khi chúng được làm sạch bởi GC? – zebrabox

+0

Nếu bạn đang được xác định, sau đó bạn sẽ muốn làm sạch bất cứ điều gì mà bạn đang * đóng gói *; đặc biệt nếu chúng là bản thân 'IDisposable'. Bạn * sẽ không * làm điều này trong finalizer, như họ có thể đã được thu thập (và: nó không phải là công việc của bạn nữa). Và bạn nói đúng; trong trường hợp này, ngoài 'SuppressFinalize' (không quan trọng lắm) chúng tôi không làm bất cứ thứ gì được quản lý, vì vậy sẽ không sao nếu không bận tâm; đó là lý do tại sao tôi nhấn mạnh trường hợp * chung *. –

7

Từ Joe Duffy's Weblog:

Đối với các lớp niêm phong, mô hình này cần không được tuân thủ, có nghĩa là bạn nên chỉ đơn giản là thực hiện finalizer của bạn và Vứt với các phương pháp đơn giản (ví dụ: ~ T() (Hoàn thành) và Vứt bỏ() trong C#). Khi chọn tuyến đường thứ hai, mã của bạn vẫn phải tuân thủ các nguyên tắc bên dưới về việc thực hiện quyết toán và xử lý logic.

Vì vậy, có, bạn nên tốt.

Bạn cần trình kết thúc như đã đề cập. Nếu bạn muốn tránh nó, bạn có thể xem SafeHandle. Tôi không có đủ kinh nghiệm với P/Invoke để đề xuất cách sử dụng chính xác.

+0

Cảm ơn TrueWill! Tôi đã xem xét SafeHandle và theo Eric Lippert nó đã được xem bởi đội BCL là một trong những lợi ích quan trọng nhất mà họ đã giới thiệu trong 'Whidbey' (xin lỗi không thể tìm thấy liên kết cho bây giờ). Thật không may nó là một lớp trừu tượng, do đó bạn phải cuộn của riêng bạn cho mọi tình huống mà kinda sucks – zebrabox

+1

@zebrabox: Trong khi bạn có thể cần phải cuộn của riêng bạn trong một số tình huống, các tài liệu tiểu bang: "Một tập hợp các lớp prewritten bắt nguồn từ SafeHandle được cung cấp như dẫn xuất trừu tượng và tập hợp này nằm trong không gian tên Microsoft.Win32.SafeHandles. " – TrueWill

+1

@TrueWill. Yep rất đúng nhưng chỉ cho các công cụ như xử lý tập tin, chờ xử lý, ống xử lý và một loạt các công cụ crypt. Vẫn còn tốt hơn là không có gì! – zebrabox

1

Bạn không thể tuyên bố phương pháp ảo trong một lớp niêm phong. Việc khai báo các thành viên được bảo vệ trong một lớp được niêm phong sẽ cho bạn một cảnh báo trình biên dịch. Vì vậy, bạn đã triển khai chính xác. Calling GC.SuppressFinalize (this) từ bên trong finalizer là không cần thiết vì lý do hiển nhiên, nhưng nó không thể làm hại.

Có trình hoàn thiện là cần thiết khi giao dịch với tài nguyên không được quản lý, vì chúng không được giải phóng tự động, bạn phải làm điều đó trong trình hoàn thiện được gọi tự động sau khi đối tượng đã được thu thập rác.

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