Question

I have an abstract base class which implements IDisposable and the full bool disposed = false, Dispose(), and Dispose(bool) pattern except for the destructor. The base class implements IDisposable since many of its derived classes need to release unmanaged resources. However, I heard that classes with destructors are expensive and thus would make the derived classes that do not have unmanaged resources unnecessarily expensive, had I included the destructor. I'm confused on this matter. Should I or should I not include the destructor and why? Thanks.

Was it helpful?

Solution

You only need to include a destructor/finalizer if you are implementing an entirely new kind of unmanaged resource. So if you're just wrapping or inheriting an existing database connection type, socket type, gdi resource, etc, then you do not need a destructor. The destructor in the original type will take care of finally releasing that resource for you. But if you are implementing something like the ADO.Net provider objects for an entirely new kind of database from scratch, then you would want to implement a destructor for your connection type, so that it can release it's connection when it is finally collected.

OTHER TIPS

Ideally the Dispose pattern relies on the finalizer as well to be complete. The reason is to be sure that the unmanaged resources will be cleaned up. The trick here is that in the Dispose method you also should have the following call: GC.SuppressFinalize(this), which instructs the garbage collector not to treat the instance in a special way, which will keep you from the overhead of finalization. So if the user works with the object correctly disposing it every time (like wrapping in every usage in a using block) then the finalizer won't be called thus not affecting the performance at all.

I heard that classes with destructors are expensive

Classes with finalizers or implementations of IDisposable are not more expensive than those without. However, a class implementing IDisposable is telling the caller that they need to be kept track of and cleaned up when no longer needed. That is additional work for the caller but the cost of not doing so is a resource leak, at least until the class is garbage collected.

In short, if your class does not use any resources that need to be cleaned up, usually in the form of fields that also implement IDisposable, you do not need a finalizer.

The only classes which should override Finalize for the purpose of cleanup are those which either derive directly from Object and expect to clean up their own resources, or things like SafeHandle, whose purpose is to manage resource cleanup. Otherwise, if a derived would have unmanaged resources that need to be cleaned up in Finalize but the base class would not, the proper approach is usually to encapsulate each separate resource in its own finalizable object. Objects which have finalizers should avoid holding strong references to any objects which are not needed for finalization, since all objects to which they hold references, as well as all objects to which any of those object hold references, etc. will be kept alive for an extra garbage-collection generation. If an object George which holds links to many other objects holds a reference to a finalizable object which does not hold a strong back-link, and George is abandoned, the finalizable object will need to be kept around for an extra GC generation but George and the other objects to which it holds direct and indirect references will not. By contrast if George itself implemented Finalize, then it and every object to which it holds a direct or indirect reference would have to be kept around.

Further, finalization can sometimes cause some rare but hard-to-track-down Hindenbugs if the last use of a large finalizable object is actually a use of one of its resources. Code which uses resources that can be cleaned up via Finalize must make sure that the object containing those resources does not become eligible for finalization while those resources are still in use. This is generally done by using GC.KeepAlive(). If a derived class adds a Finalize method that cleans up any resource that existed in the parent class but which the parent class doesn't expect a finalizer to clean up, bugs may occur. Encapsulating the resources in their own class can avoid this problem (it's possible that the parent object might get garbage-collected while the resource is in use, but that won't matter if the encapsulating object is properly designed--the encapsulating object's Finalize method won't run until its methods have finished using the resource).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top