Sử dụng đúng giao diện IDis Dùng


1658

Tôi biết từ việc đọc tài liệu của Microsoft rằng việc sử dụng "chính" của IDisposablegiao diện là để dọn sạch các tài nguyên không được quản lý.

Đối với tôi, có nghĩa là "không được quản lý" những thứ như kết nối cơ sở dữ liệu, ổ cắm, tay nắm cửa sổ, vv Nhưng, tôi đã nhìn thấy mã nơi Dispose()phương pháp được thực hiện để giải phóng được quản lý nguồn lực, mà dường như không cần thiết đối với tôi, kể từ khi thu gom rác nên chăm sóc điều đó cho bạn

Ví dụ:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

Câu hỏi của tôi là, điều này có làm cho bộ nhớ rác miễn phí được sử dụng bởi MyCollectionbất kỳ nhanh hơn bình thường không?

chỉnh sửa : Cho đến nay mọi người đã đăng một số ví dụ hay về việc sử dụng IDis Dùng để dọn sạch các tài nguyên không được quản lý như kết nối cơ sở dữ liệu và bitmap. Nhưng giả sử rằng _theListtrong đoạn mã trên có một triệu chuỗi và bạn muốn giải phóng bộ nhớ đó ngay bây giờ , thay vì chờ người thu gom rác. Các mã trên sẽ thực hiện điều đó?


34
Tôi thích câu trả lời được chấp nhận vì nó cho bạn biết 'mẫu' chính xác của việc sử dụng IDis Dùng một lần, nhưng giống như OP đã nói trong bản chỉnh sửa của mình, nó không trả lời câu hỏi dự định của anh ấy. IDis Dùng không 'gọi' GC, nó chỉ 'đánh dấu' một đối tượng là có thể phá hủy. Nhưng cách thực sự để giải phóng bộ nhớ 'ngay bây giờ' thay vì chờ đợi GC khởi động là gì? Tôi nghĩ rằng câu hỏi này xứng đáng được thảo luận nhiều hơn.
Punit Vora

40
IDisposablekhông đánh dấu bất cứ điều gì. Các Disposephương pháp làm những gì nó đã làm để làm sạch các nguồn lực được sử dụng bởi các ví dụ. Điều này không có gì để làm với GC.
John Saunders

4
@John. Tôi hiểu IDisposable. Và đó là lý do tại sao tôi nói rằng câu trả lời được chấp nhận không trả lời câu hỏi dự định của OP (và chỉnh sửa tiếp theo) về việc IDis Dùng sẽ giúp trong <i> giải phóng bộ nhớ </ i>. Vì IDisposablekhông liên quan gì đến việc giải phóng bộ nhớ, chỉ có tài nguyên, như bạn đã nói, không cần thiết phải đặt các tham chiếu được quản lý thành null ở tất cả những gì OP đang làm trong ví dụ của mình. Vì vậy, câu trả lời chính xác cho câu hỏi của anh ấy là "Không, nó không giúp giải phóng bộ nhớ nhanh hơn. Thực tế, nó không giúp ích gì cho bộ nhớ trống, chỉ là tài nguyên". Nhưng dù sao, cảm ơn cho đầu vào của bạn.
Trừng phạt Vora

9
@desigeek: nếu đây là trường hợp, thì bạn không nên nói "IDis Dùng không 'gọi' GC, nó chỉ 'đánh dấu' một đối tượng là có thể phá hủy"
John Saunders

5
@desigeek: Không có cách nào đảm bảo giải phóng bộ nhớ một cách xác định. Bạn có thể gọi GC.Collect (), nhưng đó là một yêu cầu lịch sự, không phải là nhu cầu. Tất cả các luồng đang chạy phải được tạm dừng để thu gom rác để tiếp tục - đọc khái niệm về các điểm an toàn .NET nếu bạn muốn tìm hiểu thêm, ví dụ: msdn.microsoft.com/en-us/l Library / 678ysw69 (v = vs.110 ). aspx . Nếu một luồng không thể bị treo, ví dụ vì có một lệnh gọi vào mã không được quản lý, GC.Collect () có thể không làm gì cả.
Bê tông Gannet

Câu trả lời:


2608

Quan điểm của Vứt bỏ giải phóng tài nguyên không được quản lý. Nó cần phải được thực hiện tại một số điểm, nếu không chúng sẽ không bao giờ được làm sạch. Trình thu gom rác không biết cách gọi DeleteHandle()một biến loại IntPtr, nó không biết cần gọi hay không DeleteHandle().

Lưu ý : Tài nguyên không được quản lý là gì? Nếu bạn tìm thấy nó trong Microsoft .NET Framework: nó được quản lý. Nếu bạn tự chọc vào MSDN, nó không được quản lý. Bất cứ điều gì bạn đã sử dụng P / Gọi các cuộc gọi để ra khỏi thế giới thoải mái của mọi thứ có sẵn trong .NET Framework đều không được quản lý - và giờ bạn có trách nhiệm dọn dẹp nó.

Đối tượng mà bạn đã tạo cần hiển thị một số phương thức mà thế giới bên ngoài có thể gọi để dọn sạch các tài nguyên không được quản lý. Phương pháp có thể được đặt tên theo bất cứ điều gì bạn thích:

public void Cleanup()

hoặc là

public void Shutdown()

Nhưng thay vào đó, có một tên được tiêu chuẩn hóa cho phương pháp này:

public void Dispose()

Thậm chí còn có một giao diện được tạo, IDisposablechỉ có một phương thức đó:

public interface IDisposable
{
   void Dispose()
}

Vì vậy, bạn làm cho đối tượng của mình hiển thị IDisposablegiao diện và theo cách đó bạn hứa rằng bạn đã viết phương thức duy nhất đó để dọn sạch các tài nguyên không được quản lý của bạn:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

Và bạn đã hoàn thành. Ngoại trừ bạn có thể làm tốt hơn.


Điều gì sẽ xảy ra nếu đối tượng của bạn đã phân bổ System.Drawing.Bitmap (tức là lớp Bitmap được quản lý .NET) dưới dạng một số bộ đệm khung? Chắc chắn, đây là một đối tượng .NET được quản lý và trình thu gom rác sẽ giải phóng nó. Nhưng bạn có thực sự muốn để lại 250 MB bộ nhớ chỉ ngồi đó không - chờ người thu gom rác cuối cùng xuất hiện và giải phóng nó? Nếu có kết nối cơ sở dữ liệu mở thì sao? Chắc chắn chúng tôi không muốn kết nối đó mở ra, chờ đợi GC hoàn thành đối tượng.

Nếu người dùng đã gọi Dispose()(có nghĩa là họ không còn có kế hoạch sử dụng đối tượng), tại sao không loại bỏ các bitmap lãng phí và kết nối cơ sở dữ liệu?

Vì vậy, bây giờ chúng tôi sẽ:

  • loại bỏ các tài nguyên không được quản lý (vì chúng ta phải) và
  • loại bỏ các tài nguyên được quản lý (vì chúng tôi muốn hữu ích)

Vì vậy, hãy cập nhật Dispose()phương pháp của chúng tôi để loại bỏ các đối tượng được quản lý đó:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

Và tất cả đều tốt, ngoại trừ bạn có thể làm tốt hơn !


Điều gì nếu người đó quên gọi Dispose()vào đối tượng của bạn? Sau đó, họ sẽ rò rỉ một số tài nguyên không được quản lý !

Lưu ý: Họ sẽ không rò rỉ tài nguyên được quản lý , vì cuối cùng trình thu gom rác sẽ chạy, trên một luồng nền và giải phóng bộ nhớ liên quan đến bất kỳ đối tượng không sử dụng nào. Điều này sẽ bao gồm đối tượng của bạn và bất kỳ đối tượng được quản lý nào bạn sử dụng (ví dụ: BitmapDbConnection).

Nếu người đó quên gọi Dispose(), chúng tôi vẫn có thể cứu thịt xông khói của họ! Chúng tôi vẫn có một cách để gọi nó cho họ: khi người thu gom rác cuối cùng cũng có mặt để giải phóng (tức là hoàn thiện) đối tượng của chúng tôi.

Lưu ý: Trình thu gom rác cuối cùng sẽ giải phóng tất cả các đối tượng được quản lý. Khi đó, nó gọi Finalize phương thức trên đối tượng. GC không biết, hoặc quan tâm, về phương pháp Vứt bỏ của bạn . Đó chỉ là một cái tên mà chúng tôi đã chọn cho một phương thức mà chúng tôi gọi khi chúng tôi muốn loại bỏ những thứ không được quản lý.

Sự phá hủy đối tượng của chúng ta bởi người thu gom Rác là thời điểm hoàn hảo để giải phóng những tài nguyên không được quản lý phiền phức đó. Chúng tôi làm điều này bằng cách ghi đè Finalize()phương thức.

Lưu ý: Trong C #, bạn không ghi đè rõ ràng Finalize()phương thức. Bạn viết một phương thức trông giống như một hàm hủy C ++ và trình biên dịch sẽ coi đó là triển khai Finalize()phương thức của bạn:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

Nhưng có một lỗi trong mã đó. Bạn thấy, trình thu gom rác chạy trên một luồng nền ; bạn không biết thứ tự phá hủy hai vật thể. Hoàn toàn có thể trong Dispose()mã của bạn , đối tượng được quản lý mà bạn đang cố gắng loại bỏ (vì bạn muốn trở nên hữu ích) không còn nữa:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

Vì vậy, những gì bạn cần là một cách Finalize()để nói Dispose()rằng nó không nên chạm vào bất kỳ tài nguyên được quản lý nào (vì chúng có thể không còn ở đó nữa), trong khi vẫn giải phóng các tài nguyên không được quản lý.

Mẫu chuẩn để làm điều này là có Finalize()Dispose()cả hai gọi phương thức thứ ba (!); nơi bạn vượt qua câu nói Boolean nếu bạn gọi nó từ Dispose()(trái ngược với Finalize()), nghĩa là nó an toàn đối với các tài nguyên được quản lý miễn phí.

Đây nội bộ phương pháp có thể được đưa ra một số tên tùy ý như "CoreDispose", hoặc "MyInternalDispose", nhưng là truyền thống để gọi nó là Dispose(Boolean):

protected void Dispose(Boolean disposing)

Nhưng một tên tham số hữu ích hơn có thể là:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

Và bạn thay đổi cách thực hiện IDisposable.Dispose()phương thức thành:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

và quyết toán của bạn để:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

Lưu ý : Nếu đối tượng của bạn hạ xuống từ một đối tượng thực hiện Dispose, thì đừng quên gọi phương thức Vứt bỏ cơ sở của chúng khi bạn ghi đè Vứt bỏ:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

Và tất cả đều tốt, ngoại trừ bạn có thể làm tốt hơn !


Nếu người dùng gọi Dispose()vào đối tượng của bạn, thì mọi thứ đã được dọn sạch. Sau đó, khi trình thu gom rác xuất hiện và gọi Finalize, nó sẽ gọi Disposelại.

Điều này không chỉ gây lãng phí mà còn nếu đối tượng của bạn có các tham chiếu rác đến các đối tượng bạn đã xử lý từ cuộc gọi cuối cùngDispose() , bạn sẽ cố gắng loại bỏ chúng một lần nữa!

Bạn sẽ nhận thấy trong mã của mình Tôi đã cẩn thận xóa các tham chiếu đến các đối tượng mà tôi đã xử lý, vì vậy tôi không cố gắng gọi Disposetham chiếu đối tượng rác. Nhưng điều đó không ngăn được một con bọ tinh vi chui vào.

Khi người dùng gọi Dispose(): tay cầm CthonFileBitmapIconServiceHandle bị hủy. Sau này khi trình thu gom rác chạy, nó sẽ cố gắng phá hủy cùng một xử lý một lần nữa.

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

Cách bạn khắc phục điều này là nói với người thu gom rác rằng không cần bận tâm đến việc hoàn thiện đối tượng - tài nguyên của nó đã được dọn sạch và không cần thêm công việc nữa. Bạn làm điều này bằng cách gọi GC.SuppressFinalize()trong Dispose()phương thức:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

Bây giờ người dùng đã gọi Dispose(), chúng tôi có:

  • giải phóng tài nguyên không được quản lý
  • giải phóng tài nguyên được quản lý

Không có điểm nào trong GC chạy bộ hoàn thiện - mọi thứ đều được quan tâm.

Tôi không thể sử dụng Finalize để dọn dẹp các tài nguyên không được quản lý?

Các tài liệu cho Object.Finalizebiết:

Phương thức Finalize được sử dụng để thực hiện các hoạt động dọn dẹp trên các tài nguyên không được quản lý được giữ bởi đối tượng hiện tại trước khi đối tượng bị phá hủy.

Nhưng tài liệu MSDN cũng cho biết, cho IDisposable.Dispose:

Thực hiện các tác vụ do ứng dụng xác định liên quan đến giải phóng, giải phóng hoặc đặt lại các tài nguyên không được quản lý.

Vậy đó là cái gì? Cái nào là nơi để tôi dọn dẹp các tài nguyên không được quản lý? Câu trả lời là:

Đó là lựa chọn của bạn! Nhưng chọn Dispose.

Bạn chắc chắn có thể đặt dọn dẹp không được quản lý của bạn trong trình hoàn thiện:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

Vấn đề với điều đó là bạn không biết khi nào người dọn rác sẽ đi loanh quanh để hoàn thiện đối tượng của bạn. Các tài nguyên bản địa không được quản lý, không cần thiết, không được sử dụng của bạn sẽ tồn tại xung quanh cho đến khi trình thu gom rác cuối cùng chạy. Sau đó, nó sẽ gọi phương thức hoàn thiện của bạn; làm sạch tài nguyên không được quản lý. Tài liệu của Object.Finalize chỉ ra điều này:

Thời gian chính xác khi bộ hoàn thiện thực thi là không xác định. Để đảm bảo giải phóng tài nguyên xác định cho các phiên bản của lớp của bạn, hãy triển khai phương thức Đóng hoặc cung cấp IDisposable.Disposetriển khai.

Đây là đức tính của việc sử dụng Disposeđể dọn dẹp các tài nguyên không được quản lý; bạn có thể biết và kiểm soát khi tài nguyên không được quản lý được dọn sạch. Sự hủy diệt của họ là "quyết định" .


Để trả lời câu hỏi ban đầu của bạn: Tại sao không giải phóng bộ nhớ ngay bây giờ, thay vì khi GC quyết định làm điều đó? Tôi có một phần mềm nhận dạng khuôn mặt mà nhu cầu để thoát khỏi 530 MB bộ ảnh nội tại , vì họ không còn cần thiết. Khi chúng ta không: máy ngừng hoạt động.

Đọc thưởng

Đối với bất kỳ ai thích phong cách của câu trả lời này (giải thích lý do tại sao , làm thế nào để trở nên rõ ràng), tôi khuyên bạn nên đọc Chương Một của COM Box thiết yếu của Don Box:

Trong 35 trang, anh giải thích các vấn đề của việc sử dụng các đối tượng nhị phân và phát minh ra COM trước mắt bạn. Khi bạn nhận ra lý do của COM, 300 trang còn lại là hiển nhiên và chỉ chi tiết triển khai của Microsoft.

Tôi nghĩ rằng tất cả các lập trình viên đã từng xử lý các đối tượng hoặc COM, ít nhất, nên đọc chương đầu tiên. Đó là lời giải thích tốt nhất của bất cứ điều gì bao giờ.

Đọc thêm tiền thưởng

Khi mọi thứ bạn biết là sai bởi Eric Lippert

Do đó, thực sự rất khó để viết một bộ hoàn thiện chính xác, và lời khuyên tốt nhất tôi có thể cung cấp cho bạn là không thử .


12
Bạn có thể làm tốt hơn - bạn cần thêm một cuộc gọi đến GC.SuppressFinalize () trong Dispose.
plinth

55
@Daniel Earwicker: Đó là sự thật. Microsoft rất muốn bạn ngừng sử dụng Win32 hoàn toàn và tuân thủ các cuộc gọi .NET Framework độc lập, di động, độc lập với thiết bị. Nếu bạn muốn đi chọc quanh hệ điều hành bên dưới; bởi vì bạn nghĩ rằng bạn biết hệ điều hành nào đang chạy: bạn đang tự kết liễu đời mình. Không phải mọi ứng dụng .NET đều chạy trên Windows hoặc trên máy tính để bàn.
Ian Boyd

34
Đây là một câu trả lời tuyệt vời nhưng tôi nghĩ rằng nó sẽ được hưởng lợi từ một danh sách mã cuối cùng cho một trường hợp tiêu chuẩn và cho một trường hợp mà lớp xuất phát từ một lớp cơ sở đã thực hiện Vứt bỏ. ví dụ như đã đọc ở đây ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) cũng tôi đã bị nhầm lẫn về những gì tôi nên làm gì khi phát sinh từ các lớp đã thực hiện Dispose ( này, tôi mới biết điều này)
integra753

5
@GregS và những người khác: Nói chung tôi sẽ không bận tâm đến việc thiết lập các tham chiếu đến null. Trước hết, điều đó có nghĩa là bạn không thể tạo ra chúng readonly, và thứ hai, bạn phải thực hiện !=nullkiểm tra rất xấu (như trong mã ví dụ). Bạn có thể có một lá cờ disposed, nhưng nó không dễ dàng hơn để không bận tâm về nó. .NET GC đủ mạnh để tham chiếu đến một trường xsẽ không còn được tính 'sử dụng' vào thời điểm nó đi qua x.Dispose()dòng.
porges

7
Trong trang thứ hai của cuốn sách Don Box mà bạn đã đề cập, anh ta sử dụng ví dụ về triển khai O (1) của thuật toán tìm kiếm, với "chi tiết được để lại làm bài tập cho người đọc". Tôi bật cười.
lau

65

IDisposablethường được sử dụng để khai thác usingcâu lệnh và tận dụng một cách dễ dàng để làm sạch xác định các đối tượng được quản lý.

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
Tôi thích điều đó, cá nhân, nhưng nó không thực sự hăng hái với các nguyên tắc thiết kế khung.
mqp

4
Tôi sẽ xem xét nó là thiết kế phù hợp vì nó cho phép phạm vi xác định dễ dàng và xây dựng phạm vi / dọn dẹp phạm vi, đặc biệt là khi được trộn lẫn với các khối sử dụng tài nguyên ngoại lệ, khóa và không được quản lý theo các cách phức tạp. Ngôn ngữ cung cấp điều này như là một tính năng hạng nhất.
yfeldblum

Nó không chính xác tuân theo các quy tắc được chỉ định trong FDG nhưng chắc chắn đây là cách sử dụng hợp lệ của mẫu vì nó được yêu cầu để được sử dụng bởi "tuyên bố sử dụng".
Scott Dorman

2
Miễn là Log.Outdent không ném, chắc chắn không có gì sai với điều này.
Daniel Earwicker

1
Các câu trả lời khác nhau cho việc sử dụng IDis Dùng một lần và sử dụng ID như là một phương tiện để có được hành vi phạm vi phạm vi phạm lỗi vì sự an toàn ngoại lệ? đi sâu vào chi tiết hơn một chút về lý do tại sao những người khác nhau thích / không thích kỹ thuật này. Nó hơi gây tranh cãi.
Brian

44

Mục đích của mẫu Vứt bỏ là cung cấp một cơ chế để dọn sạch cả tài nguyên được quản lý và không được quản lý và khi điều đó xảy ra phụ thuộc vào cách gọi phương thức Vứt bỏ. Trong ví dụ của bạn, việc sử dụng Vứt bỏ không thực sự làm bất cứ điều gì liên quan đến việc vứt bỏ, vì việc xóa danh sách không có tác động đến việc thu thập đó. Tương tự như vậy, các lệnh gọi để đặt các biến thành null cũng không có tác động đến GC.

Bạn có thể xem bài viết này để biết thêm chi tiết về cách triển khai mẫu Vứt bỏ, nhưng về cơ bản nó trông như thế này:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

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

Phương thức quan trọng nhất ở đây là Dispose (bool), thực sự chạy trong hai trường hợp khác nhau:

  • disposeing == true: phương thức đã được gọi trực tiếp hoặc gián tiếp bởi mã của người dùng. Tài nguyên được quản lý và không được quản lý có thể được xử lý.
  • disposeing == false: phương thức đã được gọi bởi bộ thực thi từ bên trong bộ hoàn thiện và bạn không nên tham chiếu các đối tượng khác. Chỉ các tài nguyên không được quản lý có thể được xử lý.

Vấn đề với việc đơn giản là để cho GC chăm sóc dọn dẹp là bạn không có quyền kiểm soát thực sự khi nào thì GC sẽ chạy một chu trình thu thập (bạn có thể gọi GC.Collect (), nhưng bạn thực sự không nên) vì vậy tài nguyên có thể ở lại xung quanh lâu hơn cần thiết. Hãy nhớ rằng, việc gọi Dispose () không thực sự gây ra chu kỳ thu thập hoặc theo bất kỳ cách nào khiến cho GC thu thập / giải phóng đối tượng; nó chỉ đơn giản là cung cấp các phương tiện để dọn dẹp một cách xác định hơn các tài nguyên được sử dụng và nói với GC rằng việc dọn dẹp này đã được thực hiện.

Toàn bộ quan điểm của IDis Dùng và mẫu xử lý không phải là giải phóng bộ nhớ ngay lập tức. Lần duy nhất một cuộc gọi đến Vứt bỏ thậm chí sẽ có cơ hội giải phóng bộ nhớ ngay lập tức là khi nó đang xử lý kịch bản xử lý == sai và thao túng các tài nguyên không được quản lý. Đối với mã được quản lý, bộ nhớ sẽ không thực sự được phục hồi cho đến khi GC chạy một chu trình thu thập mà bạn thực sự không kiểm soát được (ngoài việc gọi GC.Collect (), mà tôi đã đề cập không phải là một ý tưởng hay).

Kịch bản của bạn không thực sự hợp lệ vì các chuỗi trong .NET không sử dụng bất kỳ tài nguyên chưa được chỉnh sửa nào và không triển khai IDis Dùng một lần, không có cách nào để buộc chúng phải được "dọn sạch".


4
Bạn không quên thực hiện quyết toán?
Budda

@Budda: Không, anh ấy đang sử dụng SafeHandle. Không cần một kẻ hủy diệt.
Henk Holterman

9
+1 để thêm mạng an toàn cho nhiều cuộc gọi vào Dispose (). Thông số kỹ thuật cho biết nhiều cuộc gọi nên an toàn. Quá nhiều lớp Microsoft không thực hiện được điều đó và bạn sẽ gặp phải ObjectDisposedException.
Jesse Chisholm

5
Nhưng Vứt bỏ (bool dispose) là phương thức của riêng bạn trên lớp SimpleCleanup của bạn và sẽ không bao giờ được gọi bởi khung công tác. Vì bạn chỉ gọi nó với "true" là một tham số, 'xử lý' sẽ không bao giờ sai. Mã của bạn rất giống với ví dụ MSDN cho IDis Dùng một lần, nhưng thiếu trình hoàn thiện, như @Budda đã chỉ ra, đó là nơi mà cuộc gọi với disposeing = false sẽ đến từ đâu.
yoyo

19

Không nên có thêm các cuộc gọi đến các phương thức của đối tượng sau khi Dispose đã được gọi trên nó (mặc dù một đối tượng nên chấp nhận các cuộc gọi tiếp theo tới Vứt bỏ). Do đó, ví dụ trong câu hỏi là ngớ ngẩn. Nếu Dispose được gọi, thì đối tượng có thể bị loại bỏ. Vì vậy, người dùng chỉ nên loại bỏ tất cả các tham chiếu đến toàn bộ đối tượng đó (đặt chúng thành null) và tất cả các đối tượng liên quan bên trong nó sẽ tự động được dọn sạch.

Đối với câu hỏi chung về quản lý / không được quản lý và thảo luận trong các câu trả lời khác, tôi nghĩ rằng bất kỳ câu trả lời nào cho câu hỏi này phải bắt đầu bằng một định nghĩa về tài nguyên không được quản lý.

Điều nó sôi nổi là có một chức năng bạn có thể gọi để đưa hệ thống vào trạng thái, và có một chức năng khác mà bạn có thể gọi để đưa nó trở lại trạng thái đó. Bây giờ, trong ví dụ điển hình, hàm đầu tiên có thể là hàm trả về xử lý tệp và hàm thứ hai có thể là lệnh gọi CloseHandle.

Nhưng - và đây là chìa khóa - chúng có thể là bất kỳ cặp chức năng phù hợp nào. Một người xây dựng một trạng thái, người kia rơi nước mắt. Nếu trạng thái đã được xây dựng nhưng chưa bị phá hủy, thì một thể hiện của tài nguyên tồn tại. Bạn phải sắp xếp cho sự cố xảy ra vào đúng thời điểm - tài nguyên không được CLR quản lý. Loại tài nguyên được quản lý tự động duy nhất là bộ nhớ. Có hai loại: GC và stack. Các loại giá trị được quản lý bởi ngăn xếp (hoặc bằng cách đi xe bên trong các loại tham chiếu) và các loại tham chiếu được quản lý bởi GC.

Các chức năng này có thể gây ra những thay đổi trạng thái có thể được xen kẽ tự do hoặc có thể cần phải được lồng hoàn hảo. Các thay đổi trạng thái có thể là chủ đề an toàn, hoặc chúng có thể không.

Nhìn vào ví dụ trong câu hỏi của Justice. Các thay đổi đối với thụt lề của tệp Nhật ký phải được lồng hoàn hảo hoặc tất cả đều sai. Ngoài ra, chúng không có khả năng là chủ đề an toàn.

Có thể quá giang một chuyến đi với người thu gom rác để dọn dẹp các tài nguyên không được quản lý của bạn. Nhưng chỉ khi các chức năng thay đổi trạng thái là chủ đề an toàn và hai trạng thái có thể có thời gian sống trùng nhau theo bất kỳ cách nào. Vì vậy, ví dụ về tài nguyên của Justice phải KHÔNG có người hoàn thiện! Nó sẽ không giúp được ai.

Đối với những loại tài nguyên đó, bạn chỉ có thể thực hiện IDisposablemà không cần bộ hoàn thiện. Bộ hoàn thiện là hoàn toàn tùy chọn - nó phải được. Điều này được che đậy hoặc thậm chí không được đề cập trong nhiều cuốn sách.

Sau đó, bạn phải sử dụng usingcâu lệnh để có bất kỳ cơ hội đảm bảo Disposeđược gọi. Điều này về cơ bản giống như quá giang một chuyến đi với stack (vì vậy, như bộ hoàn thiện là cho GC, usinglà cho stack).

Phần còn thiếu là bạn phải viết thủ công Vứt bỏ và gọi nó vào các trường và lớp cơ sở của bạn. Các lập trình viên C ++ / CLI không phải làm điều đó. Trình biên dịch viết nó cho họ trong hầu hết các trường hợp.

Có một giải pháp thay thế, mà tôi thích cho các trạng thái lồng hoàn hảo và không phải là chủ đề an toàn (ngoài mọi thứ khác, tránh IDis Dùng cho bạn vấn đề tranh cãi với ai đó không thể chống lại việc thêm bộ hoàn thiện vào mỗi lớp thực hiện IDis Dùng) .

Thay vì viết một lớp, bạn viết một hàm. Hàm chấp nhận một đại biểu để gọi lại:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

Và sau đó, một ví dụ đơn giản sẽ là:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

Lambda được truyền vào đóng vai trò là một khối mã, do đó, giống như bạn tạo cấu trúc điều khiển của riêng mình để phục vụ cho cùng một mục đích using, ngoại trừ việc bạn không còn gặp bất kỳ nguy hiểm nào khi người gọi lạm dụng nó. Không có cách nào họ có thể không làm sạch tài nguyên.

Kỹ thuật này ít hữu ích hơn nếu tài nguyên là loại có thể có thời gian sống chồng chéo, bởi vì sau đó bạn muốn có thể xây dựng tài nguyên A, sau đó là tài nguyên B, sau đó giết tài nguyên A và sau đó giết tài nguyên B. Bạn không thể làm điều đó nếu bạn buộc người dùng phải lồng hoàn hảo như thế này. Nhưng sau đó, bạn cần sử dụng IDisposable(nhưng vẫn không có bộ hoàn thiện, trừ khi bạn đã triển khai an toàn luồng, không miễn phí).


re: "Không nên gọi thêm phương thức của đối tượng sau khi Dispose đã được gọi trên đó". "Nên" là từ hoạt động. Nếu bạn có các hành động không đồng bộ đang chờ xử lý, chúng có thể xuất hiện sau khi đối tượng của bạn đã được xử lý. Gây ra một ObjectDisposedException.
Jesse Chisholm

Dường như bạn là câu trả lời duy nhất ngoài câu trả lời của tôi về ý tưởng rằng các tài nguyên không được quản lý đóng gói trạng thái mà GC không hiểu. Tuy nhiên, một khía cạnh quan trọng của tài nguyên không được quản lý là một hoặc nhiều thực thể có trạng thái cần làm sạch trạng thái của nó có thể tiếp tục tồn tại ngay cả khi đối tượng "sở hữu" tài nguyên đó không. Làm thế nào để bạn thích định nghĩa của tôi? Khá giống nhau, nhưng tôi nghĩ nó làm cho "tài nguyên" thêm một chút danh từ (đó là "thỏa thuận" của đối tượng bên ngoài để thay đổi hành vi của nó, để đổi lấy thông báo khi dịch vụ của nó không còn cần thiết nữa)
supercat

@supercat - nếu bạn quan tâm, tôi đã viết bài đăng sau đây vài ngày sau khi tôi viết câu trả lời trên: smellegantcode.wordpress.com/2009/02/13/ Khăn
Daniel Earwicker

1
@DanielEarwicker: Bài viết thú vị, mặc dù tôi có thể nghĩ về ít nhất một loại tài nguyên không được quản lý mà bạn không thực sự bao gồm: đăng ký các sự kiện từ các đối tượng tồn tại lâu dài. Đăng ký sự kiện là nấm, nhưng ngay cả khi bộ nhớ không giới hạn để loại bỏ chúng có thể tốn kém. Ví dụ: một điều tra viên cho một bộ sưu tập cho phép sửa đổi trong quá trình liệt kê có thể cần phải đăng ký để cập nhật thông báo từ bộ sưu tập và một bộ sưu tập có thể được cập nhật nhiều lần trong vòng đời của nó. Nếu các điều tra viên bị bỏ rơi mà không hủy đăng ký ...
supercat

1
Cặp hoạt động enterexitlà cốt lõi của cách tôi nghĩ về một tài nguyên. Đăng ký / hủy đăng ký vào các sự kiện sẽ phù hợp với điều đó mà không gặp khó khăn. Xét về đặc điểm trực giao / nấm, thực tế không thể phân biệt được với rò rỉ bộ nhớ. (Điều này không có gì đáng ngạc nhiên vì đăng ký chỉ là thêm các đối tượng vào danh sách.)
Daniel Earwicker

17

Các tình huống tôi sử dụng IDis Dùng: dọn sạch các tài nguyên không được quản lý, hủy đăng ký các sự kiện, kết nối chặt chẽ

Thành ngữ tôi sử dụng để triển khai IDis Dùng ( không phải chủ đề an toàn ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

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

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

Có thể tìm thấy giải thích đầy đủ về mẫu tại msdn.microsoft.com/en-us/l
Library / b1yfkh5e.aspx

3
không bao giờ nên có một bộ hoàn thiện trừ khi bạn có tài nguyên không được quản lý. Ngay cả khi đó, việc triển khai được ưu tiên là bọc tài nguyên không được quản lý trong SafeHandle.
Dave Black

11

Đúng, mã đó là hoàn toàn dư thừa và không cần thiết và nó không làm cho trình thu gom rác làm bất cứ điều gì nó sẽ làm (nếu một trường hợp của MyCollection đi ra khỏi phạm vi, đó là.) Đặc biệt là các .Clear()cuộc gọi.

Trả lời chỉnh sửa của bạn: Sắp xếp. Nếu tôi làm điều này:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Nó có chức năng giống hệt như vậy cho mục đích quản lý bộ nhớ:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

Nếu bạn thực sự thực sự cần giải phóng bộ nhớ này ngay lập tức, hãy gọi GC.Collect(). Không có lý do để làm điều này ở đây, mặc dù. Bộ nhớ sẽ được giải phóng khi cần thiết.


2
re: "Bộ nhớ sẽ được giải phóng khi cần thiết." Thay vì nói, "khi GC quyết định nó cần thiết." Bạn có thể thấy các vấn đề về hiệu năng hệ thống trước khi GC quyết định rằng bộ nhớ là thực sự cần thiết. Giải phóng nó bây giờ có thể không cần thiết, nhưng có thể hữu ích.
Jesse Chisholm

1
Có một số trường hợp góc trong đó loại bỏ các tham chiếu trong một bộ sưu tập có thể đẩy nhanh việc thu gom rác của các mục được đề cập qua đó. Ví dụ: nếu một mảng lớn được tạo và chứa đầy các tham chiếu đến các mục mới được tạo nhỏ hơn, nhưng không lâu sau đó, việc từ bỏ mảng có thể khiến các mục đó được giữ xung quanh cho đến cấp độ 2 tiếp theo, trong khi loại bỏ nó ra trước có thể làm cho các vật phẩm đủ điều kiện cho cấp độ 0 hoặc cấp 1 tiếp theo. Chắc chắn, có những vật thể có thời gian tồn tại ngắn trên Heap đối tượng lớn dù sao cũng rất khó (tôi không thích thiết kế) nhưng ...
supercat

1
... loại bỏ các mảng như vậy trước khi từ bỏ chúng đôi khi làm giảm tác động của GC.
supercat

11

Nếu MyCollectiondù sao cũng sẽ là rác được thu gom, thì bạn không cần phải xử lý nó. Làm như vậy sẽ khiến CPU bị hỏng nhiều hơn mức cần thiết và thậm chí có thể làm mất hiệu lực một số phân tích được tính toán trước mà trình thu gom rác đã thực hiện.

Tôi sử dụng IDisposableđể làm những việc như đảm bảo các luồng được xử lý chính xác, cùng với các tài nguyên không được quản lý.

EDIT Đáp lại bình luận của Scott:

Lần duy nhất các số liệu về hiệu suất của GC bị ảnh hưởng là khi một cuộc gọi [sic] GC.Collect () được thực hiện "

Về mặt khái niệm, GC duy trì một khung nhìn của biểu đồ tham chiếu đối tượng và tất cả các tham chiếu đến nó từ các khung ngăn xếp của các luồng. Heap này có thể khá lớn và trải rộng nhiều trang bộ nhớ. Để tối ưu hóa, GC lưu trữ phân tích các trang không có khả năng thay đổi thường xuyên để tránh quét lại trang một cách không cần thiết. GC nhận được thông báo từ kernel khi dữ liệu trong trang thay đổi, vì vậy nó biết rằng trang bị bẩn và yêu cầu quét lại. Nếu bộ sưu tập ở Gen0 thì có khả năng những thứ khác trong trang cũng thay đổi, nhưng điều này ít có khả năng trong Gen1 và Gen2. Thông thường, các hook này không có sẵn trong Mac OS X cho nhóm đã chuyển GC sang Mac để có được trình cắm Silverlight hoạt động trên nền tảng đó.

Một điểm khác chống lại việc xử lý tài nguyên không cần thiết: hãy tưởng tượng một tình huống trong đó một quá trình đang dỡ tải. Cũng tưởng tượng rằng quá trình đã được chạy trong một thời gian. Rất có thể là nhiều trang bộ nhớ của quá trình đó đã được hoán đổi vào đĩa. Ít nhất là chúng không còn trong bộ đệm L1 hoặc L2. Trong tình huống như vậy, không có lý do gì để một ứng dụng không tải để hoán đổi tất cả các trang dữ liệu và mã đó trở lại bộ nhớ để 'giải phóng' tài nguyên sẽ được hệ điều hành phát hành khi quá trình kết thúc. Điều này áp dụng cho các tài nguyên không được quản lý và thậm chí nhất định. Chỉ các tài nguyên giữ cho các luồng không phải là nền phải được xử lý, nếu không thì quá trình sẽ vẫn tồn tại.

Bây giờ, trong quá trình thực thi bình thường, có các tài nguyên phù du phải được dọn sạch chính xác (như @fezmonkey chỉ ra các kết nối cơ sở dữ liệu, ổ cắm, tay cầm cửa sổ ) để tránh rò rỉ bộ nhớ không được quản lý. Đây là những thứ phải được xử lý. Nếu bạn tạo một số lớp sở hữu một luồng (và bằng cách sở hữu thì tôi có nghĩa là nó đã tạo ra nó và do đó chịu trách nhiệm đảm bảo nó dừng lại, ít nhất là theo kiểu mã hóa của tôi), thì lớp đó rất có thể phải thực hiện IDisposablevà phá bỏ luồng trong suốt Dispose.

.NET framework sử dụng IDisposablegiao diện làm tín hiệu, thậm chí cảnh báo cho các nhà phát triển rằng lớp này phải được xử lý. Tôi không thể nghĩ ra bất kỳ loại nào trong khung triển khai IDisposable(không bao gồm triển khai giao diện rõ ràng) trong đó việc xử lý là tùy chọn.


Gọi Vứt bỏ là hoàn toàn hợp lệ, hợp pháp và khuyến khích. Các đối tượng triển khai IDis Dùng thường làm như vậy vì một lý do. Lần duy nhất các số liệu về hiệu suất của GC bị ảnh hưởng là khi một cuộc gọi GC.Collect () được thực hiện.
Scott Dorman

Đối với nhiều lớp .net, việc xử lý là "hơi" tùy chọn, có nghĩa là việc từ bỏ các trường hợp "thường" sẽ không gây ra bất kỳ rắc rối nào miễn là người ta không điên cuồng tạo ra các thể hiện mới và từ bỏ chúng. Ví dụ, mã do trình biên dịch tạo cho các điều khiển dường như tạo phông chữ khi các điều khiển được khởi tạo và từ bỏ chúng khi các biểu mẫu được xử lý; nếu một người tạo và loại bỏ hàng ngàn điều khiển, điều này có thể buộc hàng ngàn tay cầm GDI, nhưng trong hầu hết các trường hợp, các điều khiển không được tạo và phá hủy nhiều như vậy. Tuy nhiên, người ta vẫn nên cố gắng tránh sự từ bỏ như vậy.
supercat

1
Trong trường hợp phông chữ, tôi nghi ngờ vấn đề là Microsoft không bao giờ thực sự xác định thực thể nào chịu trách nhiệm xử lý đối tượng "phông chữ" được gán cho một điều khiển; trong một số trường hợp, một điều khiển có thể chia sẻ một phông chữ với một đối tượng tồn tại lâu hơn, do đó, việc điều khiển Vứt bỏ phông chữ sẽ là xấu. Trong các trường hợp khác, một phông chữ sẽ được gán cho một điều khiển và không ở đâu khác, vì vậy nếu điều khiển không loại bỏ thì sẽ không có ai. Ngẫu nhiên, có thể tránh được khó khăn này với các phông chữ đã có một lớp FontTemplate không dùng một lần riêng biệt, vì các điều khiển dường như không sử dụng tay cầm GDI của Phông chữ của chúng.
supercat

Về chủ đề của các Dispose()cuộc gọi tùy chọn , xem: stackoverflow.com/questions/913228/ Kẻ
RJ Cuthbertson

7

Trong ví dụ bạn đã đăng, nó vẫn không "giải phóng bộ nhớ ngay bây giờ". Tất cả bộ nhớ là rác được thu thập, nhưng nó có thể cho phép bộ nhớ được thu thập ở thế hệ trước . Bạn sẽ phải chạy một số thử nghiệm để chắc chắn.


Nguyên tắc thiết kế khung là hướng dẫn chứ không phải quy tắc. Chúng cho bạn biết giao diện chủ yếu để làm gì, khi nào sử dụng nó, làm thế nào để sử dụng nó và khi nào không sử dụng nó.

Tôi đã từng đọc mã đó là một RollBack () đơn giản khi không sử dụng IDis Dùng một lần. Lớp MiniTx bên dưới sẽ kiểm tra cờ trên Dispose () và nếu Commitcuộc gọi không bao giờ xảy ra thì nó sẽ tự gọi Rollback. Nó đã thêm một lớp không xác định làm cho mã cuộc gọi dễ hiểu và dễ bảo trì hơn nhiều. Kết quả trông giống như:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

Tôi cũng đã thấy mã thời gian / đăng nhập làm điều tương tự. Trong trường hợp này, phương thức Dispose () đã dừng bộ hẹn giờ và ghi lại rằng khối đã thoát.

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

Vì vậy, đây là một vài ví dụ cụ thể không thực hiện bất kỳ việc dọn dẹp tài nguyên không được quản lý nào, nhưng đã sử dụng thành công IDis Dùng để tạo mã sạch hơn.


Hãy xem ví dụ của @Daniel Earwicker bằng các hàm bậc cao hơn. Đối với điểm chuẩn, thời gian, đăng nhập, vv Có vẻ đơn giản hơn nhiều.
Aluan Haddad


6

Tôi sẽ không lặp lại những thứ thông thường về việc Sử dụng hoặc giải phóng các tài nguyên không được quản lý, tất cả đã được bảo hiểm. Nhưng tôi muốn chỉ ra những gì có vẻ là một quan niệm sai lầm phổ biến.
Cho mã sau

Lớp công khai LargeStuff
  Triển khai IDis Dùng một lần
  Riêng tư _Lớn dưới dạng chuỗi ()

  'Một số mã lạ có nghĩa là _Lộng hiện chứa vài triệu chuỗi dài.

  Công khai Sub Dispose () Thực hiện IDis Dùng một lần. Mục đích
    _Lớn = Không có gì
  Kết thúc phụ

Tôi nhận ra rằng việc triển khai Dùng một lần không tuân theo các hướng dẫn hiện tại, nhưng hy vọng tất cả các bạn đều hiểu ý tưởng.
Bây giờ, khi Dispose được gọi, bao nhiêu bộ nhớ sẽ được giải phóng?

Trả lời: Không có.
Gọi Dispose có thể giải phóng các tài nguyên không được quản lý, nó KHÔNG THỂ lấy lại bộ nhớ được quản lý, chỉ có GC mới có thể làm điều đó. Điều đó không có nghĩa là những điều trên không phải là một ý tưởng tốt, theo mô hình trên vẫn là một ý tưởng tốt trong thực tế. Khi Dispose đã được chạy, sẽ không có gì ngăn cản việc xác nhận lại bộ nhớ đang được sử dụng bởi _Large, mặc dù thể hiện của LargeStuff vẫn có thể nằm trong phạm vi. Các chuỗi trong _Large cũng có thể ở gen 0 nhưng thể hiện của LargeStuff có thể là gen 2, do đó, một lần nữa, bộ nhớ sẽ được xác nhận lại sớm hơn.
Mặc dù không có điểm nào trong việc thêm một bộ hoàn thiện để gọi phương thức Vứt bỏ được hiển thị ở trên. Điều đó sẽ chỉ TRÌ việc xác nhận lại bộ nhớ để cho phép trình hoàn thiện chạy.


1
Nếu một phiên bản LargeStuffđã tồn tại đủ lâu để chuyển sang Thế hệ 2 và nếu _Largegiữ một tham chiếu đến một chuỗi mới được tạo trong Thế hệ 0, thì nếu phiên bản của LargeStuffbị bỏ qua mà không bị loại bỏ _Large, thì chuỗi được gọi bằng _Largesẽ được giữ xung quanh cho đến khi bộ sưu tập Gen2 tiếp theo. Việc loại bỏ _Largecó thể cho phép chuỗi bị loại bỏ trong bộ sưu tập Gen0 tiếp theo. Trong hầu hết các trường hợp, bỏ tham chiếu là không hữu ích, nhưng có những trường hợp nó có thể cung cấp một số lợi ích.
supercat

5

Ngoài việc sử dụng chính như một cách để kiểm soát tuổi thọ của tài nguyên hệ thống (hoàn toàn được bao phủ bởi câu trả lời tuyệt vời của Ian , kudos!), IDis Dùng / sử dụng kết hợp cũng có thể được sử dụng để xác định sự thay đổi trạng thái của tài nguyên toàn cầu (quan trọng) : các giao diện điều khiển , các chủ đề , các quá trình , bất kỳ đối tượng toàn cầu như một ví dụ ứng dụng .

Tôi đã viết một bài viết về mẫu này: http://pragmateek.com/c-scope-your-global-state-changes-with-idis Dùng-and-the-USEstatement /

Nó minh họa cách bạn có thể bảo vệ một số trạng thái toàn cầu thường được sử dụng theo cách có thể sử dụng lạicó thể đọc được : màu bàn điều khiển , văn hóa luồng hiện tại , thuộc tính đối tượng ứng dụng Excel ...


4

Nếu có bất cứ điều gì, tôi hy vọng mã sẽ kém hiệu quả hơn so với khi bỏ nó ra.

Gọi các phương thức Clear () là không cần thiết và có lẽ GC sẽ không làm điều đó nếu Dispose không làm điều đó ...


2

Có những điều mà Dispose()hoạt động thực hiện trong mã ví dụ có thể có hiệu ứng sẽ không xảy ra do một GC bình thường của MyCollectionđối tượng.

Nếu các đối tượng được tham chiếu bởi _theListhoặc _theDictđược tham chiếu bởi các đối tượng khác, thì đối tượng List<>hoặc Dictionary<>đối tượng đó sẽ không phải là đối tượng của bộ sưu tập nhưng đột nhiên sẽ không có nội dung. Nếu không có thao tác Dispose () như trong ví dụ, các bộ sưu tập đó vẫn sẽ chứa nội dung của chúng.

Tất nhiên, nếu đây là tình huống tôi sẽ gọi nó là một thiết kế bị hỏng - tôi chỉ chỉ ra (về mặt giáo dục, tôi cho rằng) Dispose()hoạt động có thể không hoàn toàn dư thừa, tùy thuộc vào việc có sử dụng khác List<>hay Dictionary<>không thể hiện trong đoạn.


Chúng là các lĩnh vực riêng tư, vì vậy tôi nghĩ thật công bằng khi cho rằng OP không đưa ra các tài liệu tham khảo cho chúng.
mqp

1) đoạn mã chỉ là mã ví dụ, vì vậy tôi chỉ chỉ ra rằng có thể có một hiệu ứng phụ dễ bị bỏ qua; 2) các trường riêng thường là mục tiêu của thuộc tính / phương thức getter - có thể quá nhiều (getter / setters được một số người coi là một chút chống mẫu).
Michael Burr

2

Một vấn đề với hầu hết các cuộc thảo luận về "tài nguyên không được quản lý" là họ không thực sự xác định thuật ngữ, nhưng dường như ngụ ý rằng nó có liên quan đến mã không được quản lý. Mặc dù đúng là nhiều loại tài nguyên không được quản lý thực hiện giao diện với mã không được quản lý, việc nghĩ đến các tài nguyên không được quản lý theo các thuật ngữ như vậy không hữu ích.

Thay vào đó, người ta nên nhận ra điểm chung của tất cả các tài nguyên được quản lý là gì thông báo mới. Nếu đối tượng bị bỏ rơi và biến mất không một dấu vết, sẽ không có gì có thể nói rằng bên ngoài 'điều' rằng nó không còn cần thiết phải thay đổi hành vi của nó thay mặt cho đối tượng không còn tồn tại; do đó, tính hữu dụng của 'thứ sẽ bị giảm vĩnh viễn.

Sau đó, một tài nguyên không được quản lý thể hiện sự thỏa thuận của một số 'vật' bên ngoài để thay đổi hành vi của nó thay mặt cho một đối tượng, điều này sẽ vô dụng làm giảm tính hữu dụng của 'vật' bên ngoài đó nếu đối tượng bị bỏ rơi và không còn tồn tại. Tài nguyên được quản lý là một đối tượng là người thụ hưởng của một thỏa thuận như vậy, nhưng đã đăng ký để nhận thông báo nếu nó bị bỏ rơi và sẽ sử dụng thông báo đó để đưa các vấn đề của mình vào trước khi nó bị phá hủy.


Vâng, IMO, định nghĩa về đối tượng không được quản lý là rõ ràng; bất kỳ đối tượng không phải là GC .
Eonil

1
@Eonil: Đối tượng không được quản lý! = Tài nguyên không được quản lý. Những thứ như các sự kiện có thể được thực hiện hoàn toàn bằng cách sử dụng các đối tượng được quản lý, nhưng vẫn tạo thành các tài nguyên không được quản lý bởi vì - ít nhất là trong trường hợp các đối tượng tồn tại trong thời gian ngắn đăng ký vào các sự kiện của các đối tượng tồn tại lâu - GC không biết gì về cách làm sạch chúng .
supercat


2

Đầu tiên của định nghĩa. Đối với tôi tài nguyên không được quản lý có nghĩa là một số lớp, thực hiện giao diện IDis Dùng hoặc một cái gì đó được tạo bằng cách sử dụng các cuộc gọi đến dll. GC không biết làm thế nào để đối phó với các đối tượng như vậy. Nếu lớp chỉ có các loại giá trị, thì tôi không coi lớp này là lớp có tài nguyên không được quản lý. Đối với mã của tôi, tôi làm theo các thực hành tiếp theo:

  1. Nếu được tạo bởi lớp tôi sử dụng một số tài nguyên không được quản lý thì điều đó có nghĩa là tôi cũng nên triển khai giao diện IDis Dùng để dọn dẹp bộ nhớ.
  2. Làm sạch đồ vật ngay khi tôi sử dụng xong.
  3. Trong phương thức xử lý của mình, tôi lặp lại tất cả các thành viên IDis Dùng một lần trong lớp và gọi Vứt bỏ.
  4. Trong phương thức Vứt bỏ của tôi, hãy gọi GC.SuppressFinalize (cái này) để thông báo cho người thu gom rác rằng đối tượng của tôi đã được dọn sạch. Tôi làm điều đó bởi vì gọi của GC là hoạt động đắt tiền.
  5. Để phòng ngừa thêm, tôi cố gắng thực hiện khả năng gọi Dispose () nhiều lần.
  6. Thỉnh thoảng tôi thêm thành viên riêng _disposed và kiểm tra các cuộc gọi phương thức đã làm đối tượng được dọn sạch. Và nếu nó đã được dọn sạch thì hãy tạo mẫu ObjectDisposedException
    Theo sau thể hiện những gì tôi mô tả bằng từ ngữ dưới dạng mẫu mã:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"Đối với tôi tài nguyên không được quản lý có nghĩa là một số lớp, thực hiện giao diện IDis Dùng hoặc một cái gì đó được tạo bằng cách sử dụng các cuộc gọi đến dll." Vì vậy, bạn đang nói rằng bất kỳ loại nào is IDisposablenên được coi là một tài nguyên không được quản lý? Điều đó có vẻ không đúng. Ngoài ra nếu loại cấy ghép là một loại giá trị thuần túy mà bạn dường như đề xuất rằng nó không cần phải được xử lý. Điều đó cũng có vẻ sai.
Aluan Haddad

Mọi người tự đánh giá. Tôi không muốn thêm vào mã của tôi một cái gì đó chỉ vì mục đích bổ sung. Điều đó có nghĩa là nếu tôi thêm IDis Dùng một lần, điều đó có nghĩa là tôi đã tạo ra một số loại chức năng mà GC không thể quản lý hoặc tôi cho rằng nó sẽ không thể quản lý trọn đời.
Yuriy Zaletskyy

2

Mẫu mã đã cho của bạn không phải là một ví dụ tốt cho IDisposableviệc sử dụng. Xóa từ điển thông thường không nên đi đến Disposephương pháp. Các mục từ điển sẽ bị xóa và xử lý khi nó đi ra khỏi phạm vi. IDisposableviệc thực hiện là cần thiết để giải phóng một số bộ nhớ / trình xử lý sẽ không giải phóng / miễn phí ngay cả khi chúng nằm ngoài phạm vi.

Ví dụ sau đây cho thấy một ví dụ tốt cho mẫu IDis Dùng một số mã và nhận xét.

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

Trường hợp sử dụng hợp lý nhất để xử lý các tài nguyên được quản lý, là chuẩn bị cho GC để lấy lại các tài nguyên mà nếu không sẽ không bao giờ được thu thập.

Một ví dụ điển hình là tài liệu tham khảo tròn.

Trong khi cách tốt nhất là sử dụng các mẫu tránh tham chiếu vòng tròn, nếu bạn kết thúc với (ví dụ) một đối tượng 'con' có tham chiếu trở lại 'cha mẹ' của nó, điều này có thể dừng bộ sưu tập cha mẹ của GC nếu bạn từ bỏ tham chiếu và dựa vào GC - cộng với nếu bạn đã triển khai bộ hoàn thiện, nó sẽ không bao giờ được gọi.

Cách duy nhất để làm điều này là phá vỡ các tham chiếu vòng tròn theo cách thủ công bằng cách đặt các tham chiếu Parent thành null trên các con.

Triển khai IDis Dùng cho phụ huynh và trẻ em là cách tốt nhất để làm điều này. Khi Vứt bỏ được gọi cho Phụ huynh, hãy gọi Vứt bỏ cho tất cả Trẻ em và trong phương thức Vứt bỏ của trẻ, đặt tham chiếu Cha mẹ thành null.


4
Đối với hầu hết các phần, GC không hoạt động bằng cách xác định các vật thể chết, mà bằng cách xác định các vật thể sống. Sau mỗi chu kỳ gc, đối với mỗi đối tượng đã đăng ký hoàn thiện, được lưu trữ trên đống đối tượng lớn hoặc là mục tiêu của một trực tiếp WeakReference, hệ thống sẽ kiểm tra một cờ cho biết rằng một tham chiếu gốc đã được tìm thấy trong chu trình GC cuối cùng và sẽ thêm đối tượng vào hàng đợi các đối tượng cần hoàn thiện ngay lập tức, giải phóng đối tượng khỏi đống đối tượng lớn hoặc vô hiệu hóa tham chiếu yếu. Refs tròn sẽ không giữ cho các đối tượng sống nếu không có ref khác.
supercat

1

Tôi thấy rất nhiều câu trả lời đã chuyển sang nói về việc sử dụng IDis Dùng cho cả tài nguyên được quản lý và không được quản lý. Tôi muốn đề xuất bài viết này là một trong những giải thích tốt nhất mà tôi đã tìm thấy về cách sử dụng IDis Dùng thực sự.

https://www.codeproject.com/Articles/29534/IDisay-What-Your-Mother-Never-Told-You- About

Đối với câu hỏi thực tế; Nếu bạn sử dụng IDis Dùng để dọn sạch các đối tượng được quản lý đang chiếm nhiều bộ nhớ, câu trả lời ngắn sẽ là không . Lý do là một khi bạn vứt bỏ IDis Dùng một lần, bạn nên để nó đi ra khỏi phạm vi. Tại thời điểm đó, bất kỳ đối tượng con tham chiếu nào cũng nằm ngoài phạm vi và sẽ được thu thập.

Ngoại lệ thực sự duy nhất cho điều này sẽ là nếu bạn có rất nhiều bộ nhớ bị ràng buộc trong các đối tượng được quản lý và bạn đã chặn luồng đó chờ một số thao tác hoàn tất. Nếu những đối tượng không cần thiết sau khi cuộc gọi đó hoàn thành thì việc đặt các tham chiếu đó thành null có thể cho phép trình thu gom rác thu thập chúng sớm hơn. Nhưng kịch bản đó sẽ đại diện cho mã xấu cần được cấu trúc lại - không phải là trường hợp sử dụng IDis Dùng.


1
Tôi không hiểu tại sao một số người đặt -1 vào câu trả lời của bạn
Sebastian Oscar Lopez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.