Đặt một đối tượng thành null so với Dispose ()


108

Tôi bị cuốn hút bởi cách hoạt động của CLR và GC (tôi đang nỗ lực mở rộng kiến ​​thức của mình về vấn đề này bằng cách đọc CLR qua C #, sách / bài đăng của Jon Skeet, v.v.).

Dù sao, sự khác biệt giữa việc nói:

MyClass myclass = new MyClass();
myclass = null;

Hoặc, bằng cách làm cho MyClass triển khai IDisposable và một trình hủy và gọi Dispose ()?

Ngoài ra, nếu tôi có một khối mã với câu lệnh using (ví dụ: bên dưới), nếu tôi bước qua mã và thoát khỏi khối using, thì đối tượng đó có được xử lý hay không hay khi thu gom rác xảy ra? Điều gì sẽ xảy ra nếu tôi gọi Dispose () trong khối using ngay sau đó?

using (MyDisposableObj mydispobj = new MyDisposableObj())
{

}

Các lớp luồng (ví dụ BinaryWriter) có một phương thức Finalize? Tại sao tôi muốn sử dụng nó?

Câu trả lời:


210

Điều quan trọng là phải tách việc xử lý ra khỏi việc thu gom rác. Chúng là những thứ hoàn toàn riêng biệt, có một điểm chung mà tôi sẽ đến sau một phút.

Dispose, thu gom và hoàn thiện rác

Khi bạn viết một usingcâu lệnh, nó chỉ đơn giản là đường cú pháp cho một khối try / final để Disposeđược gọi ngay cả khi mã trong phần thân của usingcâu lệnh ném ra một ngoại lệ. Nó không có nghĩa là đối tượng là rác được thu thập ở cuối khối.

Xử lý là về tài nguyên không được quản lý ( tài nguyên không phải bộ nhớ). Đây có thể là các điều khiển giao diện người dùng, kết nối mạng, xử lý tệp, v.v. Đây là những tài nguyên hạn chế, vì vậy bạn thường muốn phát hành chúng ngay khi có thể. Bạn nên triển khai IDisposablebất cứ khi nào kiểu của bạn "sở hữu" một tài nguyên không được quản lý, trực tiếp (thường là thông qua một IntPtr) hoặc gián tiếp (ví dụ: thông qua a Stream, a, SqlConnectionv.v.).

Bản thân việc thu gom rác chỉ là về bộ nhớ - với một chút thay đổi nhỏ. Bộ thu gom rác có thể tìm các đối tượng không còn có thể tham chiếu được nữa và giải phóng chúng. Mặc dù vậy, nó không tìm kiếm rác - chỉ khi nó phát hiện ra rằng nó cần (ví dụ: nếu một "thế hệ" của heap hết bộ nhớ).

Vòng xoắn đang hoàn thiện . Trình thu gom rác giữ một danh sách các đối tượng không còn có thể truy cập được nữa, nhưng có một trình hoàn thiện (được viết bằng ~Foo()C #, hơi khó hiểu - chúng không giống như các trình hủy C ++). Nó chạy các trình hoàn thiện trên các đối tượng này, đề phòng trường hợp chúng cần dọn dẹp thêm trước khi bộ nhớ của chúng được giải phóng.

Finalizers hầu như luôn được sử dụng để dọn dẹp tài nguyên trong trường hợp người dùng loại này đã quên vứt bỏ nó một cách có trật tự. Vì vậy, nếu bạn mở một FileStreamnhưng quên gọi Disposehoặc Close, trình hoàn thiện cuối cùng sẽ phát hành trình xử lý tệp cơ bản cho bạn. Theo ý kiến ​​của tôi, trong một chương trình được viết tốt, những người hoàn thiện hầu như không bao giờ được kích hoạt.

Đặt một biến thành null

Một điểm nhỏ khi đặt biến thành null- điều này hầu như không bao giờ bắt buộc vì lợi ích của việc thu gom rác. Đôi khi bạn có thể muốn làm điều đó nếu đó là một biến thành viên, mặc dù theo kinh nghiệm của tôi, hiếm khi "một phần" của một đối tượng không còn cần thiết nữa. Khi đó là một biến cục bộ, JIT thường đủ thông minh (ở chế độ phát hành) để biết khi nào bạn sẽ không sử dụng lại tham chiếu. Ví dụ:

StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();

// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);

// These aren't helping at all!
x = null;
sb = null;

// Assume that x and sb aren't used here

Thời điểm mà bạn thể đáng giá để đặt một biến cục bộ nulllà khi bạn đang ở trong một vòng lặp và một số nhánh của vòng lặp cần sử dụng biến nhưng bạn biết rằng bạn đã đạt đến điểm mà bạn không. Ví dụ:

SomeObject foo = new SomeObject();

for (int i=0; i < 100000; i++)
{
    if (i == 5)
    {
        foo.DoSomething();
        // We're not going to need it again, but the JIT
        // wouldn't spot that
        foo = null;
    }
    else
    {
        // Some other code 
    }
}

Triển khai IDisposable / finalizers

Vì vậy, các loại của riêng bạn có nên triển khai trình hoàn thiện không? Gần như chắc chắn là không. Nếu bạn chỉ gián tiếp nắm giữ các tài nguyên không được quản lý (ví dụ: bạn đã có một FileStreambiến là thành viên) thì việc thêm trình hoàn thiện của riêng bạn sẽ không giúp ích gì: luồng gần như chắc chắn sẽ đủ điều kiện để thu gom rác khi đối tượng của bạn có, vì vậy bạn chỉ có thể dựa vào FileStreamcó trình hoàn thiện (nếu cần - nó có thể đề cập đến thứ khác, v.v.). Nếu bạn muốn nắm giữ một tài nguyên không được quản lý "gần như" trực tiếp, đó SafeHandlelà bạn của bạn - bạn phải mất một chút thời gian để tiếp tục, nhưng điều đó có nghĩa là bạn sẽ gần như không bao giờ cần viết lại bản hoàn thiện . Bạn thường chỉ cần một bản hoàn thiện nếu bạn thực sự có khả năng xử lý trực tiếp một tài nguyên (an IntPtr) và bạn nên chuyển sangSafeHandleCàng sớm càng tốt. (Có hai liên kết ở đó - lý tưởng là hãy đọc cả hai.)

Joe Duffy có một bộ hướng dẫn rất dài về những người hoàn thiện và IDisposable (được viết chung với rất nhiều người thông minh) rất đáng đọc. Cần lưu ý rằng nếu bạn niêm phong các lớp của mình, nó sẽ làm cho cuộc sống dễ dàng hơn rất nhiều: mẫu ghi đè Disposeđể gọi một Dispose(bool)phương thức ảo mới, v.v. chỉ phù hợp khi lớp của bạn được thiết kế để kế thừa.

Điều này hơi lan man, nhưng vui lòng yêu cầu làm rõ nơi bạn muốn một số :)


Re "Một lần mà nó có thể đáng giá để đặt một biến cục bộ thành null" - có lẽ cũng là một số trường hợp "bắt" gai góc hơn (nhiều lần chụp cùng một biến) - nhưng nó có thể không đáng làm phức tạp bài đăng! +1 ...
Marc Gravell

@Marc: Đó là sự thật - tôi thậm chí còn không nghĩ đến các biến được nắm bắt. Hừ! Vâng, tôi nghĩ tôi sẽ để điều đó một mình;)
Jon Skeet

bạn có thể vui lòng cho biết điều gì sẽ xảy ra khi bạn đặt "foo = null" trong đoạn mã của mình ở trên không? Theo như tôi biết, dòng đó chỉ xóa giá trị của một biến trỏ đến đối tượng foo trong heap được quản lý? vì vậy câu hỏi là điều gì sẽ xảy ra với đối tượng foo ở đó? chúng ta không nên gọi là vứt bỏ nó?
odiseh

@odiseh: Nếu đối tượng dùng một lần thì có - bạn nên vứt bỏ nó. Phần câu trả lời đó chỉ đề cập đến vấn đề thu gom rác, hoàn toàn riêng biệt.
Jon Skeet

1
Tôi đang tìm kiếm sự giải thích rõ ràng về một số mối quan tâm về IDisposable, vì vậy tôi đã tìm kiếm "IDisposable Skeet" và tìm thấy điều này. Tuyệt quá! : D
Maciej Wozniak

22

Khi bạn loại bỏ một đối tượng, các tài nguyên sẽ được giải phóng. Khi bạn gán null cho một biến, bạn chỉ đang thay đổi một tham chiếu.

myclass = null;

Sau khi bạn thực thi điều này, đối tượng mà myclass đề cập đến vẫn tồn tại và sẽ tiếp tục cho đến khi GC hoàn thành việc dọn dẹp nó. Nếu Dispose được gọi một cách rõ ràng hoặc nó nằm trong một khối đang sử dụng, thì mọi tài nguyên sẽ được giải phóng càng sớm càng tốt.


7
có thể không tồn tại sau khi thực hiện dòng đó - nó có thể đã được thu gom rác trước dòng đó. JIT rất thông minh - việc tạo ra các dòng như thế này hầu như luôn không liên quan.
Jon Skeet

6
Đặt thành null có thể có nghĩa là tài nguyên do đối tượng nắm giữ sẽ không bao giờ được giải phóng. GC không xử lý, nó chỉ hoàn thiện, vì vậy nếu đối tượng trực tiếp giữ các tài nguyên không được quản lý và trình hoàn thiện của nó không xử lý (hoặc nó không có trình hoàn thiện) thì các tài nguyên đó sẽ bị rò rỉ. Vài điều cần lưu ý.
LukeH

6

Hai hoạt động không liên quan nhiều đến nhau. Khi bạn đặt một tham chiếu đến null, nó chỉ đơn giản thực hiện điều đó. Bản thân nó không ảnh hưởng đến lớp được tham chiếu. Biến của bạn chỉ đơn giản là không còn trỏ đến đối tượng mà nó đã từng sử dụng, nhưng bản thân đối tượng không thay đổi.

Khi bạn gọi Dispose (), đó là một cuộc gọi phương thức trên chính đối tượng. Bất cứ điều gì phương thức Dispose làm, bây giờ được thực hiện trên đối tượng. Nhưng điều này không ảnh hưởng đến tham chiếu của bạn đến đối tượng.

Khu vực chồng chéo duy nhất là khi không còn tham chiếu đến một đối tượng, cuối cùng nó sẽ được thu gom rác. Và nếu lớp triển khai giao diện IDisposable, thì Dispose () sẽ được gọi trên đối tượng trước khi nó được thu gom rác.

Nhưng điều đó sẽ không xảy ra ngay lập tức sau khi bạn đặt tham chiếu của mình thành null, vì hai lý do. Thứ nhất, các tham chiếu khác có thể tồn tại, vì vậy nó sẽ không được thu gom rác và thứ hai, ngay cả khi đó là tham chiếu cuối cùng, vì vậy nó hiện đã sẵn sàng để thu gom rác, sẽ không có gì xảy ra cho đến khi bộ thu gom rác quyết định xóa đối tượng.

Gọi Dispose () trên một đối tượng không "giết" đối tượng theo bất kỳ cách nào. Nó thường được sử dụng để dọn dẹp để đối tượng có thể được xóa một cách an toàn sau đó, nhưng cuối cùng, không có gì kỳ diệu về Dispose, nó chỉ là một phương thức lớp.


Tôi nghĩ rằng câu trả lời này khen ngợi hoặc là một chi tiết cho câu trả lời của "đệ quy".
dance2die 22/02/09
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.