Tại sao một số người sử dụng Finalize
phương pháp trên Dispose
phương pháp?
Trong tình huống nào bạn sẽ sử dụng Finalize
phương thức trên Dispose
phương thức và ngược lại?
Tại sao một số người sử dụng Finalize
phương pháp trên Dispose
phương pháp?
Trong tình huống nào bạn sẽ sử dụng Finalize
phương thức trên Dispose
phương thức và ngược lại?
Câu trả lời:
Những người khác đã đề cập đến sự khác biệt giữa Dispose
và Finalize
(btw Finalize
phương thức vẫn được gọi là hàm hủy trong đặc tả ngôn ngữ), vì vậy tôi sẽ chỉ thêm một chút về các tình huống mà Finalize
phương thức này có ích.
Một số loại đóng gói các tài nguyên dùng một lần theo cách dễ sử dụng và loại bỏ chúng trong một hành động. Cách sử dụng chung thường như thế này: mở, đọc hoặc viết, đóng (Vứt bỏ). Nó rất phù hợp với using
cấu trúc.
Những người khác là một chút khó khăn hơn. WaitEventHandles
đối với các trường hợp không được sử dụng như thế này vì chúng được sử dụng để báo hiệu từ luồng này sang luồng khác. Câu hỏi sau đó trở thành ai nên kêu gọi Dispose
những điều này? Vì các kiểu bảo vệ như thế này thực hiện một Finalize
phương thức, điều này đảm bảo các tài nguyên được xử lý khi cá thể không còn được ứng dụng tham chiếu nữa.
Finalize
có thể được biện minh là khi có một số đối tượng quan tâm đến việc giữ tài nguyên tồn tại, nhưng không có cách nào mà một đối tượng ngừng quan tâm đến tài nguyên có thể tìm ra nếu đó là cái cuối cùng. Trong trường hợp như vậy, Finalize
thường sẽ chỉ bắn khi không ai quan tâm đến đối tượng. Thời gian lỏng lẻo Finalize
là khủng khiếp đối với các tài nguyên không bị nấm như tệp và khóa, nhưng có thể ổn đối với tài nguyên có thể bị nấm.
Phương thức hoàn thiện được gọi khi đối tượng của bạn là rác được thu thập và bạn không có gì đảm bảo khi điều này xảy ra (bạn có thể buộc nó, nhưng nó sẽ làm giảm hiệu suất).
Mặt khác, Dispose
phương thức này được gọi là mã đã tạo lớp của bạn để bạn có thể dọn sạch và giải phóng bất kỳ tài nguyên nào bạn có được (dữ liệu không được quản lý, kết nối cơ sở dữ liệu, xử lý tệp, v.v.) ngay khi mã được thực hiện đối tượng của bạn.
Thực hành tiêu chuẩn là thực hiện IDisposable
và Dispose
để bạn có thể sử dụng đối tượng của mình trong một thống kê using
. Chẳng hạn như using(var foo = new MyObject()) { }
. Và trong trình hoàn thiện của bạn, bạn gọi Dispose
, chỉ trong trường hợp mã cuộc gọi quên không xử lý bạn.
Hoàn thiện là phương thức backstop, được gọi bởi trình thu gom rác khi nó lấy lại một đối tượng. Vứt bỏ là phương pháp "dọn dẹp xác định", được các ứng dụng gọi để giải phóng các tài nguyên bản địa có giá trị (tay cầm cửa sổ, kết nối cơ sở dữ liệu, v.v.) khi chúng không còn cần thiết, thay vì để chúng giữ vô thời hạn cho đến khi GC đi vòng quanh đối tượng.
Là người dùng của một đối tượng, bạn luôn sử dụng Vứt bỏ. Hoàn thiện là dành cho GC.
Là người triển khai của một lớp, nếu bạn giữ các tài nguyên được quản lý phải được xử lý, bạn sẽ thực hiện Vứt bỏ. Nếu bạn giữ tài nguyên riêng, bạn triển khai cả Vứt bỏ và Hoàn thiện và cả hai đều gọi một phương thức chung giải phóng tài nguyên bản địa. Các thành ngữ này thường được kết hợp thông qua một phương thức Dispose (bool dispose) riêng, loại bỏ các cuộc gọi với true và Hoàn tất các cuộc gọi với false. Phương pháp này luôn giải phóng các tài nguyên bản địa, sau đó kiểm tra tham số xử lý và nếu đúng, nó xử lý các tài nguyên được quản lý và gọi GC.SuppressFinalize.
Dispose
là tốt, và thực hiện nó một cách chính xác nói chung là dễ dàng. Finalize
là xấu xa, và thực hiện nó một cách chính xác nói chung là khó khăn. Trong số những thứ khác, bởi vì GC sẽ đảm bảo rằng không có danh tính của đối tượng sẽ được "tái chế" miễn là có bất kỳ tham chiếu nào đến đối tượng đó, nên việc dọn sạch một loạt các Disposable
đối tượng, một số trong đó có thể đã được làm sạch, là không vấn đề gì; mọi tham chiếu đến một đối tượng Dispose
đã được gọi sẽ vẫn là một tham chiếu đến một đối tượng mà nó Dispose
đã được gọi.
Fred
sở hữu tệp xử lý số 42 và đóng nó, hệ thống có thể đính kèm số đó vào một số xử lý tệp được trao cho một số thực thể khác. Trong trường hợp đó, tệp xử lý số 42 sẽ không đề cập đến tệp đã đóng của Fred, mà là tệp được sử dụng bởi thực thể khác đó; cho Fred
để cố gắng gần gũi xử lý # 42 một lần nữa sẽ là thảm hoạ. Cố gắng 100% đáng tin cậy để theo dõi xem một đối tượng không được quản lý đã được phát hành có khả thi hay không. Cố gắng theo dõi nhiều đối tượng khó hơn nhiều.
Hoàn thiện
protected
, không public
hoặc private
để phương thức không thể được gọi trực tiếp từ mã của ứng dụng và đồng thời, nó có thể thực hiện cuộc gọi đến base.Finalize
phương thứcVứt bỏ
IDisposable
trên mọi loại có bộ hoàn thiệnDispose
phương thức. Nói cách khác, tránh sử dụng một đối tượng sau khi Dispose
phương thức đã được gọi trên nó.Dispose
tất cả các IDisposable
loại khi bạn đã hoàn thành với chúngDispose
được gọi nhiều lần mà không tăng lỗi.Dispose
phương thức bằng GC.SuppressFinalize
phương thứcDispose
các phương thứcVứt bỏ / Hoàn thiện mẫu
Dispose
và Finalize
khi làm việc với các tài nguyên không được quản lý. Việc Finalize
triển khai sẽ chạy và các tài nguyên vẫn sẽ được giải phóng khi đối tượng là rác được thu thập ngay cả khi nhà phát triển bỏ qua việc gọi Dispose
phương thức một cách rõ ràng.Finalize
phương pháp cũng như Dispose
phương pháp. Ngoài ra, gọi Dispose
phương thức cho bất kỳ đối tượng .NET nào mà bạn có như các thành phần bên trong lớp đó (có tài nguyên không được quản lý làm thành viên của chúng) từ Dispose
phương thức.Hoàn thiện được gọi bởi GC khi đối tượng này không còn được sử dụng.
Vứt bỏ chỉ là một phương thức bình thường mà người dùng của lớp này có thể gọi để giải phóng bất kỳ tài nguyên nào.
Nếu người dùng quên gọi Vứt bỏ và nếu lớp đã Hoàn tất triển khai thì GC sẽ đảm bảo nó được gọi.
Có một số chìa khóa trong cuốn sách Bộ công cụ chứng nhận MCSD (kỳ thi 70-483) trang 193:
hàm hủy (gần như bằng)base.Finalize()
, hàm hủy được chuyển đổi thành phiên bản ghi đè của phương thức Finalize thực thi mã của hàm hủy và sau đó gọi phương thức Finalize của lớp cơ sở. Sau đó, nó hoàn toàn không mang tính quyết định mà bạn không thể biết khi nào sẽ được gọi vì phụ thuộc vào GC.
Nếu một lớp không chứa tài nguyên được quản lý và không có tài nguyên không được quản lý , thì nó không nên thực hiện IDisposable
hoặc có hàm hủy.
Nếu lớp chỉ có các tài nguyên được quản lý , nó sẽ thực hiện IDisposable
nhưng nó không nên có hàm hủy. (Khi hàm hủy thực thi, bạn không thể chắc chắn các đối tượng được quản lý vẫn tồn tại, do đó bạn không thể gọi Dispose()
phương thức của chúng bằng mọi cách.)
Nếu lớp chỉ có các tài nguyên không được quản lý , nó cần thực hiện IDisposable
và cần một hàm hủy trong trường hợp chương trình không gọi Dispose()
.
Dispose()
phương pháp phải an toàn để chạy nhiều lần. Bạn có thể đạt được điều đó bằng cách sử dụng một biến để theo dõi xem nó đã được chạy trước đó chưa.
Dispose()
nên giải phóng cả tài nguyên được quản lý và không được quản lý .
Các hàm hủy chỉ nên giải phóng các tài nguyên không được quản lý . Khi hàm hủy thực thi, bạn không thể chắc chắn các đối tượng được quản lý vẫn tồn tại, do đó bạn không thể gọi các phương thức Vứt bỏ của chúng bằng mọi cách. Điều này có được bằng cách sử dụng protected void Dispose(bool disposing)
mẫu chính tắc , trong đó chỉ các tài nguyên được quản lý được giải phóng (xử lý) khi disposing == true
.
Sau khi giải phóng tài nguyên, Dispose()
nên gọiGC.SuppressFinalize
, để đối tượng có thể bỏ qua hàng đợi quyết toán.
Một ví dụ về việc triển khai cho một lớp với các tài nguyên không được quản lý và quản lý:
using System;
class DisposableClass : IDisposable
{
// A name to keep track of the object.
public string Name = "";
// Free managed and unmanaged resources.
public void Dispose()
{
FreeResources(true);
// We don't need the destructor because
// our resources are already freed.
GC.SuppressFinalize(this);
}
// Destructor to clean up unmanaged resources
// but not managed resources.
~DisposableClass()
{
FreeResources(false);
}
// Keep track if whether resources are already freed.
private bool ResourcesAreFreed = false;
// Free resources.
private void FreeResources(bool freeManagedResources)
{
Console.WriteLine(Name + ": FreeResources");
if (!ResourcesAreFreed)
{
// Dispose of managed resources if appropriate.
if (freeManagedResources)
{
// Dispose of managed resources here.
Console.WriteLine(Name + ": Dispose of managed resources");
}
// Dispose of unmanaged resources here.
Console.WriteLine(Name + ": Dispose of unmanaged resources");
// Remember that we have disposed of resources.
ResourcesAreFreed = true;
}
}
}
99% thời gian, bạn cũng không cần phải lo lắng. :) Nhưng, nếu các đối tượng của bạn giữ các tham chiếu đến các tài nguyên không được quản lý (ví dụ xử lý cửa sổ, xử lý tệp), bạn cần cung cấp một cách để đối tượng được quản lý của bạn giải phóng các tài nguyên đó. Hoàn thiện cho phép kiểm soát ngầm phát hành tài nguyên. Nó được gọi bởi người thu gom rác. Vứt bỏ là một cách để cung cấp quyền kiểm soát rõ ràng đối với việc phát hành tài nguyên và có thể được gọi trực tiếp.
Có nhiều hơn nữa để tìm hiểu về chủ đề của Bộ sưu tập rác , nhưng đó là một sự khởi đầu.
Công cụ hoàn thiện là để dọn dẹp ngầm - bạn nên sử dụng nó bất cứ khi nào một lớp quản lý các tài nguyên hoàn toàn phải được dọn sạch vì nếu không bạn sẽ rò rỉ tay cầm / bộ nhớ, v.v ...
Việc triển khai chính xác một bộ hoàn thiện là rất khó khăn và nên tránh ở bất cứ nơi nào có thể - SafeHandle
lớp (có sẵn trong .Net v2.0 trở lên) có nghĩa là bạn rất hiếm khi (nếu có) cần phải thực hiện một bộ hoàn thiện nữa.
Các IDisposable
giao diện là để dọn dẹp rõ ràng và được sử dụng nhiều hơn nữa thường - bạn nên sử dụng này cho phép người dùng để phát hành một cách rõ ràng hoặc nguồn lực dọn dẹp bất cứ khi nào họ đã hoàn thành sử dụng một đối tượng.
Lưu ý rằng nếu bạn có bộ hoàn thiện thì bạn cũng nên triển khai IDisposable
giao diện để cho phép người dùng giải phóng rõ ràng những tài nguyên đó sớm hơn so với khi đối tượng bị thu gom rác.
Xem Cập nhật DG: Loại bỏ, quyết toán và quản lý tài nguyên để biết những gì tôi cho là tập hợp khuyến nghị tốt nhất và đầy đủ nhất về quyết toán và IDisposable
.
Tóm tắt là -
Ngoài ra, một sự khác biệt khác là - trong triển khai Dispose (), bạn cũng nên giải phóng các tài nguyên được quản lý , trong khi điều đó không nên được thực hiện trong Finalizer. Điều này là do rất có thể các tài nguyên được quản lý được tham chiếu bởi đối tượng đã được dọn sạch trước khi nó sẵn sàng để được hoàn thành.
Đối với một lớp sử dụng các tài nguyên không được quản lý, cách tốt nhất là định nghĩa cả hai - phương thức Dispose () và Finalizer - để được sử dụng như một dự phòng trong trường hợp nhà phát triển quên loại bỏ rõ ràng đối tượng. Cả hai đều có thể sử dụng phương pháp chia sẻ để dọn sạch các tài nguyên được quản lý và không được quản lý: -
class ClassWithDisposeAndFinalize : IDisposable
{
// Used to determine if Dispose() has already been called, so that the finalizer
// knows if it needs to clean up unmanaged resources.
private bool disposed = false;
public void Dispose()
{
// Call our shared helper method.
// Specifying "true" signifies that the object user triggered the cleanup.
CleanUp(true);
// Now suppress finalization to make sure that the Finalize method
// doesn't attempt to clean up unmanaged resources.
GC.SuppressFinalize(this);
}
private void CleanUp(bool disposing)
{
// Be sure we have not already been disposed!
if (!this.disposed)
{
// If disposing equals true i.e. if disposed explicitly, dispose all
// managed resources.
if (disposing)
{
// Dispose managed resources.
}
// Clean up unmanaged resources here.
}
disposed = true;
}
// the below is called the destructor or Finalizer
~ClassWithDisposeAndFinalize()
{
// Call our shared helper method.
// Specifying "false" signifies that the GC triggered the cleanup.
CleanUp(false);
}
Ví dụ tốt nhất mà tôi biết.
public abstract class DisposableType: IDisposable
{
bool disposed = false;
~DisposableType()
{
if (!disposed)
{
disposed = true;
Dispose(false);
}
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
Dispose(true);
GC.SuppressFinalize(this);
}
}
public void Close()
{
Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// managed objects
}
// unmanaged objects and resources
}
}
Khác nhau giữa các phương thức Hoàn thiện và Loại bỏ trong C #.
GC gọi phương thức hoàn thiện để lấy lại các tài nguyên không được quản lý (như thao tác tệp, windows api, kết nối mạng, kết nối cơ sở dữ liệu) nhưng thời gian không được cố định khi GC sẽ gọi nó. Nó được gọi là ngầm bởi nó có nghĩa là chúng tôi không có quyền kiểm soát cấp thấp đối với nó.
Phương pháp loại bỏ: Chúng tôi có quyền kiểm soát mức độ thấp đối với nó khi chúng tôi gọi nó từ mã. chúng tôi có thể lấy lại các tài nguyên không được quản lý bất cứ khi nào chúng tôi cảm thấy không thể sử dụng được. Chúng tôi có thể đạt được điều này bằng cách triển khai mẫu IDisposed.
Các thể hiện của lớp thường đóng gói kiểm soát các tài nguyên không được quản lý bởi thời gian chạy, chẳng hạn như các điều khiển cửa sổ (HWND), các kết nối cơ sở dữ liệu, v.v. Do đó, bạn nên cung cấp cả một cách rõ ràng và một cách ngầm để giải phóng những tài nguyên đó. Cung cấp điều khiển ngầm bằng cách triển khai Phương thức Hoàn thiện được bảo vệ trên một đối tượng (cú pháp hàm hủy trong C # và Phần mở rộng được quản lý cho C ++). Trình thu gom rác gọi phương thức này tại một số điểm sau khi không còn bất kỳ tham chiếu hợp lệ nào cho đối tượng. Trong một số trường hợp, bạn có thể muốn cung cấp cho các lập trình viên sử dụng một đối tượng với khả năng giải phóng rõ ràng các tài nguyên bên ngoài này trước khi trình thu gom rác giải phóng đối tượng. Nếu một nguồn tài nguyên bên ngoài khan hiếm hoặc đắt tiền, hiệu suất tốt hơn có thể đạt được nếu lập trình viên giải phóng rõ ràng tài nguyên khi chúng không còn được sử dụng. Để cung cấp kiểm soát rõ ràng, hãy triển khai phương thức Vứt bỏ do Giao diện IDis cung cấp. Người tiêu dùng của đối tượng nên gọi phương thức này khi nó được thực hiện bằng cách sử dụng đối tượng. Vứt bỏ có thể được gọi ngay cả khi các tham chiếu khác đến đối tượng còn sống.
Lưu ý rằng ngay cả khi bạn cung cấp kiểm soát rõ ràng bằng cách Loại bỏ, bạn nên cung cấp dọn dẹp ngầm bằng phương pháp Hoàn thiện. Finalize cung cấp một bản sao lưu để ngăn chặn tài nguyên bị rò rỉ vĩnh viễn nếu lập trình viên không gọi được Dispose.
Sự khác biệt chính giữa Vứt bỏ và Hoàn thiện là:
Dispose
thường được gọi bởi mã của bạn. Các tài nguyên được giải phóng ngay lập tức khi bạn gọi nó. Mọi người quên gọi phương thức, vì vậy using() {}
câu lệnh được phát minh. Khi chương trình của bạn kết thúc việc thực thi mã bên trong {}
, nó sẽ Dispose
tự động gọi phương thức.
Finalize
không được gọi bởi mã của bạn. Nó có nghĩa là được gọi bởi Garbage Collector (GC). Điều đó có nghĩa là tài nguyên có thể được giải phóng bất cứ lúc nào trong tương lai bất cứ khi nào GC quyết định làm như vậy. Khi GC thực hiện công việc của mình, nó sẽ trải qua nhiều phương thức Hoàn thiện. Nếu bạn có logic nặng trong việc này, nó sẽ làm cho quá trình chậm lại. Nó có thể gây ra vấn đề hiệu suất cho chương trình của bạn. Vì vậy, hãy cẩn thận về những gì bạn đặt trong đó.
Cá nhân tôi sẽ viết phần lớn logic phá hủy trong Vứt bỏ. Hy vọng, điều này sẽ làm sáng tỏ sự nhầm lẫn.
Như chúng ta đã biết, xử lý và hoàn thiện cả hai đều được sử dụng để giải phóng tài nguyên không được quản lý .. nhưng sự khác biệt là hoàn thiện sử dụng hai chu kỳ để giải phóng tài nguyên, trong khi xử lý sử dụng một chu kỳ ..
Để trả lời về phần đầu tiên, bạn nên cung cấp các ví dụ nơi mọi người sử dụng cách tiếp cận khác nhau cho cùng một đối tượng lớp chính xác. Nếu không thì rất khó (hoặc thậm chí là lạ) để trả lời.
Đối với câu hỏi thứ hai, trước tiên hãy đọc tốt hơn Giao diện IDis Dùng để xác nhận rằng
Đó là sự lựa chọn của bạn! Nhưng chọn Vứt bỏ.
Nói cách khác: GC chỉ biết về bộ hoàn thiện (nếu có. Còn được gọi là hàm hủy đối với Microsoft). Một mã tốt sẽ cố gắng dọn sạch từ cả hai (bộ hoàn thiện và Loại bỏ).