Khi nào tôi nên tạo một hàm hủy?


185

Ví dụ:

public class Person
{
    public Person()
    {
    }

    ~Person()
    {
    }
}

Khi nào tôi nên tự tạo một hàm hủy? Khi nào bạn cần tạo một hàm hủy?


1
Ngôn ngữ C # gọi đây là "hàm hủy", nhưng hầu hết mọi người gọi chúng là "bộ hoàn thiện" vì đó là tên .NET của chúng và nó làm giảm sự nhầm lẫn với các hàm hủy C ++ (khá khác nhau). Cách triển khai IDis Dùng và Hoàn tất: 3 Quy tắc dễ dàng
Stephen Cleary

5
Khi bạn đang cảm thấy liều lĩnh.
capdragon

32
Tôi nghĩ bàn phím của TomTom bị trục trặc. Các khóa mũ được diễn ra lẻ tẻ. Kỳ dị.
Jeff LaFay


Cuối cùng tôi đã sử dụng một hàm hủy như một công cụ gỡ lỗi dựa trên đề xuất của Greg Beech: stackoverflow.com/questions/3832911/ Kẻ
Brian

Câu trả lời:


237

CẬP NHẬT: Câu hỏi này là chủ đề của blog của tôi vào tháng 5 năm 2015 . Cảm ơn vì câu hỏi tuyệt vời của bạn! Xem blog để biết danh sách dài những sai lầm mà mọi người thường tin về quyết toán.

Khi nào tôi nên tự tạo một hàm hủy?

Hầu như không bao giờ.

Thông thường, người ta chỉ tạo ra một hàm hủy khi lớp của bạn đang giữ một số tài nguyên không được quản lý đắt tiền phải được dọn sạch khi đối tượng biến mất. Tốt hơn là sử dụng mẫu dùng một lần để đảm bảo rằng tài nguyên được dọn sạch. Một hàm hủy sau đó về cơ bản là một sự đảm bảo rằng nếu người tiêu dùng đối tượng của bạn quên xử lý nó, tài nguyên cuối cùng vẫn được dọn sạch. (Có lẽ.)

Nếu bạn tạo một hàm hủy thì cực kỳ cẩn thậnhiểu cách thức hoạt động của trình thu gom rác . Kẻ hủy diệt thực sự kỳ lạ :

  • Họ không chạy trên chủ đề của bạn; họ chạy trên chủ đề riêng của họ. Đừng gây ra bế tắc!
  • Một ngoại lệ chưa được xử lý ném từ một kẻ hủy diệt là tin xấu. Đó là trên chủ đề riêng của nó; ai sẽ bắt nó
  • Một hàm hủy có thể được gọi trên một đối tượng sau khi hàm tạo khởi động nhưng trước khi hàm tạo kết thúc. Một hàm hủy được viết đúng sẽ không dựa vào các bất biến được thiết lập trong hàm tạo.
  • Một kẻ hủy diệt có thể "hồi sinh" một vật thể, làm cho một vật thể chết sống lại. Thật kỳ quặc. Đừng làm điều đó.
  • Một kẻ hủy diệt có thể không bao giờ chạy; bạn không thể dựa vào đối tượng đã từng được lên lịch để hoàn thiện. Nó có thể sẽ được, nhưng đó không phải là một sự đảm bảo.

Hầu như không có gì là bình thường đúng là đúng trong một hàm hủy. Hãy thực sự, thực sự cẩn thận. Viết một hàm hủy chính xác là rất khó.

Khi nào bạn cần tạo một hàm hủy?

Khi kiểm tra một phần của trình biên dịch xử lý các hàm hủy. Tôi chưa bao giờ cần phải làm như vậy trong mã sản xuất. Tôi hiếm khi viết các đối tượng thao túng tài nguyên không được quản lý.


"Một hàm hủy có thể được gọi trên một đối tượng sau khi hàm tạo khởi động nhưng trước khi hàm tạo kết thúc." Nhưng tôi có thể dựa vào các trình khởi tạo trường để chạy, phải không?
cấu hình

13
@configurator: Không. Giả sử trình khởi tạo trường thứ ba của một đối tượng có bộ hoàn thiện được gọi là phương thức tĩnh khiến cho một ngoại lệ bị ném. Khi nào trình khởi tạo trường thứ tư sẽ chạy? Không bao giờ. Nhưng đối tượng vẫn được phân bổ và phải được hoàn thiện. Heck, bạn thậm chí không có một đảm bảo rằng các trường loại đôi được khởi tạo hoàn toàn khi dtor chạy. Có thể đã có một chủ đề hủy bỏ giữa chừng bằng cách viết cú đúp và bây giờ trình hoàn thiện phải xử lý một nửa nhân đôi nửa không khởi tạo.
Eric Lippert

1
Bài đăng tuyệt vời, nhưng nên nói "nên được tạo khi lớp của bạn đang giữ một số đối tượng không được quản lý đắt tiền hoặc khiến một số lượng lớn các đối tượng không được quản lý tồn tại" - Ví dụ cụ thể, tôi có một lớp ma trận trong C # sử dụng C ++ gốc bên dưới lớp ma trận để thực hiện nhiều công việc nặng nhọc - Tôi tạo ra rất nhiều ma trận - một "kẻ hủy diệt" vượt trội hơn nhiều so với IDis Dùng trong trường hợp cụ thể này, bởi vì nó giữ cho các mặt được quản lý và không được quản lý của ngôi nhà đồng bộ tốt hơn
Mark Mullin

1
pythonnet sử dụng hàm hủy để giải phóng GIL trong CPython không được quản lý
denfromufa

3
Bài viết tuyệt vời Eric. Đạo cụ cho việc này -> "Thêm phần thưởng thú vị: thời gian chạy sử dụng việc tạo mã ít tích cực hơn và thu gom rác ít tích cực hơn khi chạy chương trình trong trình gỡ lỗi, bởi vì đó là một trải nghiệm gỡ lỗi tồi khi có các đối tượng mà bạn đang gỡ lỗi đột nhiên biến mất mặc dù biến tham chiếu đến đối tượng nằm trong phạm vi. Điều đó có nghĩa là nếu bạn gặp lỗi trong đó một đối tượng đang được hoàn thiện quá sớm, có lẽ bạn không thể sao chép lỗi đó trong trình gỡ lỗi! "
Ken Palmer

17

Nó được gọi là "bộ hoàn thiện" và bạn thường chỉ nên tạo một cái cho một lớp có trạng thái (ví dụ: các trường) bao gồm các tài nguyên không được quản lý (tức là: con trỏ để xử lý truy xuất thông qua các cuộc gọi p / gọi). Tuy nhiên, trong .NET 2.0 trở lên, thực sự có một cách tốt hơn để xử lý việc dọn sạch các tài nguyên không được quản lý: SafeHandle . Với điều này, bạn sẽ không bao giờ cần phải viết một bộ hoàn thiện nữa.


25
@ThomasEding - Đúng vậy . C # sử dụng cú pháp hàm hủy, nhưng thực tế nó đang tạo một bộ hoàn thiện . Một lần nữa .
JDB vẫn còn nhớ Monica

@JDB: Cấu trúc ngôn ngữ được gọi là hàm hủy. Tôi không thích cái tên đó, nhưng đó là tên của nó. Hành động khai báo một hàm hủy làm cho trình biên dịch tạo ra một phương thức hoàn thiện chứa một ít mã trình bao bọc cùng với bất cứ thứ gì xuất hiện trong phần thân của hàm hủy.
supercat

8

Bạn không cần một cái trừ khi lớp của bạn duy trì các tài nguyên không được quản lý như các tệp Windows xử lý.


5
Chà, thực ra, nó được gọi là kẻ hủy diệt
David Heffernan

2
Bây giờ tôi đang bối rối. Nó là hoàn thiện hay hủy diệt?

4
Thông số kỹ thuật C # trong thực tế gọi nó là một hàm hủy. Một số người coi đây là một sai lầm. stackoverflow.com/questions/1872700/ Mạnh
Ani

2
@ThomasEding - Đúng vậy . C # sử dụng cú pháp hàm hủy, nhưng thực tế nó đang tạo một bộ hoàn thiện .
JDB vẫn còn nhớ Monica

2
Tôi thích những bình luận ở đây, panto thật :)
Stewol

4

Nó được gọi là bộ hủy / bộ hoàn thiện và thường được tạo khi triển khai mẫu Loại bỏ.

Đó là một giải pháp dự phòng khi người dùng trong lớp của bạn quên gọi Dispose, để đảm bảo rằng (cuối cùng) tài nguyên của bạn sẽ được giải phóng, nhưng bạn không có bất kỳ đảm bảo nào khi gọi hàm hủy.

Trong câu hỏi Stack Overflow này , câu trả lời được chấp nhận cho thấy chính xác cách triển khai mẫu xử lý. Điều này chỉ cần thiết nếu lớp của bạn chứa bất kỳ tài nguyên không được khai thác nào mà trình thu gom rác không quản lý để tự dọn sạch.

Một thực hành tốt là không triển khai bộ hoàn thiện mà không cung cấp cho người dùng của lớp khả năng Xử lý đối tượng theo cách thủ công để giải phóng tài nguyên ngay lập tức.


Trên thực tế, nó KHÔNG được gọi là hàm hủy trong C # với lý do chính đáng.
TomTom

14
Thật ra nó là vậy . Cảm ơn đã cho tôi một downvote vì bạn đang nhầm. Xem thư viện MSDN về vấn đề cụ thể này: msdn.microsoft.com/en-us/l
yvind Bråthen

1
@TomTom tên chính thức của nó là kẻ hủy diệt
David Heffernan

Đây thực sự không phải là một phương thức dự phòng, nó chỉ đơn giản cho phép quản lý GC khi các đối tượng của bạn giải phóng tài nguyên không được quản lý, việc triển khai IDis Dùng cho phép bạn tự quản lý.
HasaniH

3

Khi bạn có tài nguyên không được quản lý và bạn cần chắc chắn rằng chúng sẽ được dọn sạch khi đối tượng của bạn biến mất. Ví dụ tốt sẽ là các đối tượng COM hoặc Trình xử lý tệp.


2

Tôi đã sử dụng một hàm hủy (chỉ dành cho mục đích gỡ lỗi) để xem liệu một đối tượng có bị xóa khỏi bộ nhớ trong phạm vi của ứng dụng WPF hay không. Tôi không chắc liệu bộ sưu tập rác có thực sự thanh lọc đối tượng khỏi bộ nhớ hay không, và đây là một cách tốt để xác minh.


1

Công cụ hủy cấu trúc cung cấp một cách ngầm để giải phóng các tài nguyên không được quản lý được gói gọn trong lớp của bạn, chúng được gọi khi GC đi xung quanh nó và chúng gọi ngầm phương thức Finalize của lớp cơ sở. Nếu bạn đang sử dụng nhiều tài nguyên không được quản lý, tốt hơn là cung cấp một cách rõ ràng để giải phóng các tài nguyên đó thông qua giao diện IDis Dùng. Xem hướng dẫn lập trình C #: http://msdn.microsoft.com/en-us/l Library / 66x5fx1b.aspx

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.