Sự khác biệt giữa việc sử dụng IDisposable và một hàm hủy trong C # là gì?


101

Khi nào tôi sẽ triển khai IDispose trên một lớp thay vì một hàm hủy? Tôi đọc bài báo này , nhưng tôi vẫn còn thiếu điểm.

Giả định của tôi là nếu tôi triển khai IDispose trên một đối tượng, tôi có thể 'hủy' nó một cách rõ ràng thay vì đợi bộ thu gom rác thực hiện. Điều này có chính xác?

Điều đó có nghĩa là tôi phải luôn gọi rõ ràng là Dispose trên một đối tượng? Một số ví dụ phổ biến về điều này là gì?


5
Thật vậy, bạn nên gọi Dispose trên mọi đối tượng dùng một lần. Bạn có thể làm điều đó dễ dàng bằng cách sử dụng usingcấu trúc.
Luc Touraille

Ah, có lý. Tôi đã luôn tự hỏi tại sao câu lệnh 'using' lại được sử dụng cho các luồng tệp. Tôi biết nó có liên quan đến phạm vi của đối tượng, nhưng tôi đã không đặt nó trong ngữ cảnh với giao diện IDisposable.
Jordan Parmer

5
Một điểm quan trọng cần nhớ là trình hoàn thiện không bao giờ được truy cập vào bất kỳ thành viên nào được quản lý của một lớp, vì những thành viên đó có thể không còn là tham chiếu hợp lệ.
Dan Bryant

Câu trả lời:


126

Bộ hoàn thiện (hay còn gọi là bộ hủy) là một phần của bộ sưu tập rác (GC) - không xác định được khi nào (hoặc thậm chí nếu) điều này xảy ra, vì GC chủ yếu xảy ra do áp lực bộ nhớ (tức là cần thêm dung lượng). Trình hoàn thiện thường chỉ được sử dụng để dọn dẹp tài nguyên không được quản lý , vì tài nguyên được quản lý sẽ có bộ sưu tập / xử lý riêng.

Do đó IDisposableđược sử dụng để làm sạch các đối tượng một cách xác định , tức là bây giờ. Nó không thu thập bộ nhớ của đối tượng (vẫn thuộc về GC) - nhưng được sử dụng để đóng tệp, kết nối cơ sở dữ liệu, v.v.

Có rất nhiều chủ đề trước đây về điều này:

Cuối cùng, hãy lưu ý rằng không có gì lạ khi một IDisposableđối tượng cũng có phần hoàn thiện; trong trường hợp này, Dispose()thường là các cuộc gọi GC.SuppressFinalize(this), nghĩa là GC không chạy trình hoàn thiện - nó chỉ đơn giản là ném bộ nhớ đi (rẻ hơn nhiều). Trình hoàn thiện vẫn chạy nếu bạn quên Dispose()đối tượng.


Cảm ơn! Điều đó có ý nghĩa hoàn hảo. Tôi đánh giá cao phản ứng tuyệt vời.
Jordan Parmer

27
Một điều cần nói thêm. Đừng thêm một trình hoàn thiện vào lớp học của bạn trừ khi bạn thực sự, thực sự cần. Nếu bạn thêm một trình hoàn thiện (hủy), GC phải gọi nó (ngay cả một trình hoàn thiện rỗng) và để gọi nó, đối tượng sẽ luôn tồn tại sau một lần thu gom rác thế hệ 1. Điều này sẽ cản trở và làm chậm GC. Đó là wht Marc nói để gọi SuppressFinalize trong đoạn mã trên
Kevin Jones

1
Vì vậy, Finalize là giải phóng các tài nguyên không được quản lý. Nhưng Dispose có thể được sử dụng để giải phóng các tài nguyên được quản lý và không được quản lý?
Dark_Knight

2
@Dark có; bởi vì 6 cấp độ xuống chuỗi quản lý có thể là một cấp độ không được quản lý cần được dọn dẹp nhanh chóng
Marc Gravell

1
@KevinJones Các đối tượng có bộ hoàn thiện được đảm bảo tồn tại ở thế hệ 0 chứ không phải 1, phải không? Tôi đã đọc điều đó trong một cuốn sách có tên .NET Performance.
David Klempfner

25

Vai trò của Finalize()phương thức là đảm bảo rằng một đối tượng .NET có thể dọn dẹp các tài nguyên không được quản lý khi rác được thu thập . Tuy nhiên, các đối tượng như kết nối cơ sở dữ liệu hoặc trình xử lý tệp nên được phát hành càng sớm càng tốt, thay vì dựa vào việc thu gom rác. Đối với điều đó, bạn nên triển khai IDisposablegiao diện và giải phóng tài nguyên của bạn trong Dispose()phương pháp.


9

Có một mô tả rất tốt trên MSDN :

Công dụng chính của giao diện này là giải phóng các tài nguyên không được quản lý . Bộ thu gom rác tự động giải phóng bộ nhớ được cấp phát cho một đối tượng được quản lý khi đối tượng đó không còn được sử dụng nữa. Tuy nhiên, không thể đoán trước được thời điểm thu gom rác . Hơn nữa, trình thu gom rác không có kiến ​​thức về các tài nguyên không được quản lý như tay cầm cửa sổ hoặc các tệp và luồng đang mở .

Sử dụng phương pháp Dispose của giao diện này để giải phóng rõ ràng các tài nguyên không được quản lý cùng với bộ thu gom rác. Người tiêu dùng một đối tượng có thể gọi phương thức này khi đối tượng không còn cần thiết nữa.


1
Một điểm yếu chính của mô tả đó là MS đưa ra các ví dụ về tài nguyên không được quản lý, nhưng từ những gì tôi thấy chưa bao giờ thực sự định nghĩa thuật ngữ này. Vì các đối tượng được quản lý thường chỉ có thể sử dụng trong mã được quản lý, người ta có thể nghĩ rằng những thứ được sử dụng trong mã không được quản lý là tài nguyên không được quản lý, nhưng điều đó không thực sự đúng. Rất nhiều mã không được quản lý không sử dụng bất kỳ tài nguyên nào và một số loại tài nguyên không được quản lý như sự kiện chỉ tồn tại trong vũ trụ mã được quản lý.
supercat

1
Nếu một đối tượng tồn tại trong thời gian ngắn đăng ký một sự kiện từ một đối tượng tồn tại lâu dài (ví dụ: nó yêu cầu được thông báo về bất kỳ thay đổi nào xảy ra trong thời gian tồn tại của đối tượng ngắn hạn), thì một sự kiện đó phải được coi là tài nguyên không được quản lý, vì không Việc hủy đăng ký sự kiện sẽ khiến thời gian tồn tại của đối tượng tồn tại ngắn hạn được kéo dài thành thời gian tồn tại của đối tượng tồn tại lâu dài. Nếu nhiều nghìn hoặc hàng triệu đối tượng tồn tại trong thời gian ngắn đã đăng ký một sự kiện nhưng bị bỏ rơi mà không hủy đăng ký, điều đó có thể gây ra rò rỉ bộ nhớ hoặc CPU (vì thời gian cần thiết để xử lý mỗi đăng ký sẽ tăng lên).
supercat

1
Một kịch bản khác liên quan đến tài nguyên không được quản lý trong mã được quản lý sẽ là phân bổ đối tượng từ các nhóm. Đặc biệt nếu mã cần chạy trong .NET Micro Framework (có trình thu gom rác kém hiệu quả hơn nhiều so với trên máy tính để bàn), thì mã có thể hữu ích khi mã có một mảng cấu trúc, mỗi cấu trúc có thể được đánh dấu là "đã qua sử dụng" hoặc "miễn phí". Một yêu cầu cấp phát phải tìm một cấu trúc hiện được đánh dấu là "miễn phí", đánh dấu là "đã sử dụng" và trả về một chỉ mục cho nó; một yêu cầu phát hành phải đánh dấu một cấu trúc là "miễn phí". Nếu một yêu cầu phân bổ trả về ví dụ: 23, thì ...
supercat

1
... nếu mã không bao giờ thông báo cho chủ sở hữu của mảng rằng nó không cần mục # 23 nữa, thì vị trí mảng đó sẽ không bao giờ có thể sử dụng được bởi bất kỳ mã nào khác. Việc phân bổ thủ công như vậy ra khỏi các khe cắm mảng không được sử dụng thường xuyên trong mã máy tính để bàn vì GC khá hiệu quả, nhưng trong mã chạy trên Micro Framework, nó có thể tạo ra sự khác biệt rất lớn.
supercat

8

Điều duy nhất nên có trong một trình hủy C # là dòng này:

Dispose(False);

Đó là nó. Không có gì khác nên có trong phương pháp đó.


3
Đây là mẫu thiết kế do Microsoft đề xuất trong tài liệu .NET, nhưng đừng sử dụng nó khi đối tượng của bạn không phải là IDisposable. msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl

1
Tôi không thể nghĩ ra bất kỳ lý do gì để cung cấp một lớp có trình hoàn thiện cũng không có phương thức Dispose.
Jonathan Allen

4

Câu hỏi của bạn về việc bạn có nên gọi luôn hay không Disposethường là một cuộc tranh luận sôi nổi. Xem blog này để biết góc nhìn thú vị từ những cá nhân được tôn trọng trong cộng đồng .NET.

Cá nhân tôi nghĩ quan điểm của Jeffrey Richter rằng việc gọi điện Disposekhông bắt buộc là cực kỳ yếu. Ông đưa ra hai ví dụ để biện minh cho ý kiến ​​của mình.

Trong ví dụ đầu tiên, anh ấy nói việc gọi Disposecác điều khiển Windows Forms là tẻ nhạt và không cần thiết trong các tình huống chính thống. Tuy nhiên, ông không đề cập đến việc Disposethực sự được gọi tự động bởi các vùng chứa điều khiển trong các kịch bản chính đó.

Trong ví dụ thứ hai, anh ta nói rằng một nhà phát triển có thể giả định không chính xác rằng phiên bản từ IAsyncResult.WaitHandlenên được xử lý mạnh mẽ mà không nhận ra rằng thuộc tính khởi tạo bộ xử lý chờ một cách lười biếng dẫn đến một hình phạt hiệu suất không cần thiết. Tuy nhiên, vấn đề với ví dụ này là IAsyncResultbản thân nó không tuân thủ các nguyên tắc đã xuất bản của Microsoft để xử lý IDisposablecác đối tượng. Đó là nếu một lớp giữ một tham chiếu đến một IDisposablekiểu thì bản thân lớp đó sẽ thực thi IDisposable. Nếu IAsyncResulttuân theo quy tắc đó thì Disposephương pháp riêng của nó có thể đưa ra quyết định về việc thành viên nào trong số các thành viên của nó cần phải xử lý.

Vì vậy, trừ khi ai đó có lập luận thuyết phục hơn, tôi sẽ ở lại trại "luôn gọi là vứt bỏ" với sự hiểu biết rằng sẽ có một số trường hợp ngoài lề phát sinh chủ yếu do các lựa chọn thiết kế kém.


3

Nó thực sự khá đơn giản. Tôi biết nó đã được trả lời nhưng tôi sẽ thử lại nhưng sẽ cố gắng giữ nó đơn giản nhất có thể.

Nói chung không bao giờ được sử dụng trình hủy. Nó chỉ được chạy .net muốn nó chạy. Nó sẽ chỉ chạy sau một chu kỳ thu gom rác. Nó có thể không bao giờ thực sự được chạy trong vòng đời ứng dụng của bạn. Vì lý do này, bạn không nên đặt bất kỳ mã nào vào một trình hủy mà 'phải' được chạy. Bạn cũng không thể dựa vào bất kỳ đối tượng hiện có nào trong lớp để tồn tại khi nó chạy (chúng có thể đã được dọn dẹp vì thứ tự mà trình hủy chạy không được đảm bảo).

IDisposible nên được sử dụng bất cứ khi nào bạn có một đối tượng tạo tài nguyên cần dọn dẹp (ví dụ: xử lý tệp và đồ họa). Trên thực tế, nhiều người cho rằng bất cứ thứ gì bạn đặt trong một trình hủy đều nên được đưa vào IDisposable do những lý do được liệt kê ở trên.

Hầu hết các lớp sẽ gọi vứt bỏ khi trình hoàn thiện được thực thi nhưng điều này chỉ đơn giản là ở đó như một biện pháp bảo vệ an toàn và không bao giờ được dựa vào. Bạn nên loại bỏ rõ ràng bất kỳ thứ gì triển khai IDisposable khi bạn hoàn thành việc đó. Nếu bạn thực hiện IDisposable, bạn nên gọi vứt bỏ trong trình hoàn thiện. Xem http://msdn.microsoft.com/en-us/library/system.idisposable.aspx để biết ví dụ.


Không, bộ thu gom rác không bao giờ gọi Dispose (). Nó chỉ gọi người hoàn thiện.
Marc Gravell

Đã sửa điều đó. Các lớp có nhiệm vụ gọi tiêu hủy trong bản tổng kết của họ, nhưng họ không cần phải làm vậy.
DaEagle

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.