2009-02-22 38 views
93

Tôi bị cuốn hút bởi cách CLR và GC hoạt động (tôi đang mở rộng kiến ​​thức về điều này bằng cách đọc CLR qua C#, sách/bài đăng của Jon Skeet và hơn thế nữa).Đặt đối tượng thành null và Dispose()

Dù sao, sự khác biệt giữa nói là gì:

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

Hoặc, bằng cách làm cho MyClass thực hiện IDisposable và một destructor và gọi Dispose()?

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

using (MyDisposableObj mydispobj = new MyDisposableObj()) 
{ 

} 

Các lớp học trực tuyến (ví dụ: BinaryWriter) có phương pháp Hoàn tất? Tại sao tôi muốn sử dụng nó?

Trả lời

185

Điều quan trọng là phải tách riêng khỏi việc thu gom rác thải. Chúng hoàn toàn riêng biệt, với một điểm chung mà tôi sẽ đến trong một phút.

Dispose, thu gom rác thải và quyết toán

Khi bạn viết một tuyên bố using, đó là đường đơn giản là cú pháp cho một thử/cuối cùng khối để Dispose được gọi là thậm chí nếu mã trong cơ thể của báo cáo kết quả using ném 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ý khoảng tài nguyên không được quản lý (tài nguyên không phải bộ nhớ). Đây có thể là xử lý giao diện người dùng, kết nối mạng, xử lý tệp, vv Đây là những tài nguyên giới hạn, do đó, bạn thường muốn phát hành chúng ngay khi có thể. Bạn nên triển khai IDisposable bất cứ khi nào loại của bạn "sở hữu" tài nguyên không được quản lý, trực tiếp (thường là qua số IntPtr) hoặc gián tiếp (ví dụ: qua số Stream, số SqlConnection v.v.).

Bộ sưu tập rác chỉ là về bộ nhớ - với một chút xoắn. Bộ thu gom rác có thể tìm thấy các đối tượng không thể tham chiếu được nữa và giải phóng chúng. Tuy nhiên, không phải lúc nào cũng tìm kiếm rác - chỉ khi nó phát hiện ra nó cần (ví dụ: nếu một "thế hệ" của heap hết bộ nhớ).

Xoay là quyết toán. Bộ 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 chỉnh (được viết là ~Foo() trong C#, hơi gây nhầm lẫn - chúng không giống như các trình phá hủy C++). Nó chạy finalizers trên các đối tượng này, chỉ trong trường hợp họ cần phải làm thêm dọn dẹp trước khi bộ nhớ của họ được giải phóng.

Trình tổng hợp hầu như luôn được sử dụng để xóa tài nguyên trong trường hợp người dùng thuộc loại đã quên bỏ nó theo cách có trật tự. Vì vậy, nếu bạn mở một số FileStream nhưng quên gọi Dispose hoặc Close, trình kết thúc sẽ cuối cùng là giải phóng xử lý tệp cơ bản cho bạn. Trong một chương trình tốt bằng văn bản, finalizers hầu như không bao giờ cháy theo ý kiến ​​của tôi.

Thiết lập một biến để null

Một điểm nhỏ về thiết một biến để null - điều này gần như không bao giờ yêu cầu vì lợi ích của thu gom rác thải. Đôi khi bạn có thể muốn làm điều đó nếu nó là một biến thành viên, mặc dù trong kinh nghiệm của tôi hiếm khi "phần" của một đối tượng không còn cần thiết nữa. Khi đó là biến cục bộ, JIT thường đủ thông minh (trong 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 

Một lần, nơi nó có thể có giá trị thiết lập một biến địa phương để null là khi bạn đang ở trong một vòng lặp, và một số chi nhánh của vòng lặp cần phải sử dụng biến nhưng bạn biết bạn đã đạt đến một điểm mà bạn không đạt được. 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 
    } 
} 

thực hiện IDisposable/finalizers

Vì vậy, nên loại riêng của bạn thực hiện finalizers? Hầu như chắc chắn là không. Nếu bạn chỉ gián tiếp giữ tài nguyên không được quản lý (ví dụ:bạn có một biến finalizer của bạn sẽ không giúp ích gì: luồng này gần như chắc chắn sẽ đủ điều kiện thu gom rác khi đối tượng của bạn, vì vậy bạn chỉ có thể dựa vào FileStream có trình hoàn thiện (nếu cần - nó có thể ám chỉ cái gì khác, v.v.). Nếu bạn muốn giữ một tài nguyên không được quản lý "gần" trực tiếp, SafeHandle là bạn của bạn - phải mất một chút thời gian để bắt đầu, nhưng điều đó có nghĩa là bạn sẽ almostnever need to write a finalizer again. Bạn thường chỉ cần một finalizer nếu bạn có một thực sự trực tiếp xử lý trên một nguồn lực (một IntPtr) và bạn nên xem xét để di chuyển đến SafeHandle càng sớm càng tốt. (Có hai liên kết ở đó - đọc cả hai, lý tưởng.)

Joe Duffy có very long set of guidelines around finalizers and IDisposable (đồng viết với rất nhiều dân gian thông minh) đáng đọc. Điều đáng lưu ý là nếu bạn niêm phong các lớp của mình, nó giúp cuộc sống trở nên dễ dàng hơn nhiều: mẫu ghi đè Dispose để gọi phương thức ảo Dispose(bool) ảo mới chỉ có liên quan khi lớp của bạn được thiết kế để kế thừa.

Đây là một chút của một nói dông dài, nhưng hãy hỏi để làm rõ nơi bạn muốn một số :)

+0

Re "Một lần mà nó có thể được thiết lập giá trị một biến địa phương để null" - có lẽ cũng một số các kịch bản "nắm bắt" gai (nhiều chụp cùng biến) - nhưng nó có thể không có giá trị phức tạp các bài viết! +1 ... –

+0

@Marc: Đúng vậy - tôi thậm chí còn không nghĩ đến các biến bị bắt. Hmm. Vâng, tôi nghĩ tôi sẽ để nó một mình;) –

+9

Wow. Cuối cùng tôi đã hiểu được nguồn gốc của giáo phái Skeetists. Bài đăng này thật tuyệt vời! – JohnFx

19

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

myclass = null; 

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

+7

Nó * có thể * không còn tồn tại sau khi thực hiện dòng đó - nó có thể đã được thu thập rác * trước * dòng đó. JIT là những dòng sản xuất thông minh như thế này hầu như không liên quan. –

+6

Thiết lập để null có thể có nghĩa là tài nguyên được tổ chức bởi đối tượng là * không bao giờ * giải phóng. GC không xử lý, nó chỉ hoàn thành, vì vậy nếu đối tượng trực tiếp nắm giữ các tài nguyên không được quản lý và trình kết thúc của nó không vứt bỏ (hoặc nó không có trình hoàn thành) thì các tài nguyên đó sẽ bị rò rỉ. Một cái gì đó để nhận thức được. – LukeH

4

Hai thao tác này không liên quan nhiều đến nhau. Khi bạn đặt tham chiếu thành null, nó chỉ đơn giản là làm điều đó. Nó không tự nó ảnh hưởng đến lớp học được tham chiếu cả. Biến của bạn đơn giản không còn trỏ tới đố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à cuộc gọi phương thức trên chính đối tượng đó. Bất kể phương thức Dispose nào cũng đượ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 với đối tượng.

Khu vực duy nhất chồng chéo là khi không có thêm tham chiếu đến đối tượng, nó sẽ cuối cùng thu gom rác. Và nếu lớp thực hiện giao diện IDisposable, thì Dispose() sẽ được gọi trên đối tượng trước khi nó được thu thập 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 thành null, vì hai lý do. Đầu tiên, các tham chiếu khác có thể tồn tại, vì vậy nó sẽ không thu gom rác, và thứ hai, ngay cả khi đó là tham chiếu cuối cùng, vì vậy 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 để làm sạch để các đố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ì huyền diệu về Vứt bỏ, nó chỉ là một phương pháp lớp.

+0

Tôi nghĩ rằng câu trả lời này là lời khen hoặc là một chi tiết cho câu trả lời của "đệ quy". – Sung

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