Every now and then I get an email from a reader or a customer, who asks for clarifications on object finalization and disposing. As far as I know, the best article on this topic is this essay by Joe Duffy. It's over 25-page long, covers both .NET 1.1 and 2.0, and includes comments from gurus such as Jeffrey Richter e Chris Brumme. This is easily the definitive article on this topic and I urge you to read it if you haven't already.
The Dispose-Finalize pattern is objectively a complex matter. However, in most cases it can be simplified significantly if you use the following approach: (1) the class with the Dispose/Finalize method should wrap only one single unmanaged resource, and (2) this finalizable class should be private and nested inside another disposable (but not finalizable) type. The outer class is the only class that can use the finalizable class.
This simple trick enables the GC to immediately release all the memory used by the wrapper (disposable) class even in the worst case - that is, if the client code omits to invoke the Dispose method - and simplifies the structure of the type that uses the unmanaged resource. A listing is worth one thousand words, thus here is the C# version of what I mean:
// the class that clients use to work with the unmanaged resourceclass WinResource : IDisposable{ // private field that creates a wrapper for the unmanaged resource private UnmanagedResourceWrapper wrapper = null; // this is true if the object has been disposed of bool disposed = false; public WinResource(string someData) { // allocate the unmanaged resource here wrapper = new UnmanagedResourceWrapper(someData); } // a public method that clients call to work with the unmanaged resource public void DoSomething() { // throw if the object has been already disposed of if ( disposed ) throw new ObjectDisposedException(""); // this code can pass the wrapper.Handle value to API calls. // ... } public void Dispose() { // avoid issues when multiple threads call Dispose at the same time. lock ( this ) { // do nothing if already disposed of if ( disposed ) return; // dispose of all the disposable objects used by this instance // including the one that wraps the unmanaged resource // ... wrapper.Dispose(); // remember this object has been disposed of disposed = true; } } // the nested private class that allocates and release the unmanaged resource private sealed class UnmanagedResourceWrapper : IDisposable { // an invalid handle value, that the wrapper class can use to check // whether the handle is valid public static readonly IntPtr InvalidHandle = new IntPtr(-1); // a public field, but accessible only from inside the WinResource class public IntPtr Handle = InvalidHandle; // the constructor takes some data and allocates the unmanaged resource (eg a file) public UnmanagedResourceWrapper(string someData) { // this is just a demo... this.Handle = new IntPtr(12345); } // the Dispose method can be invoked only by WinResource class public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } // the finalizer ~UnmanagedResourceWrapper() { Dispose(false); } // This is where the unmanaged resource is actually disposed of. // Notice that it takes an argument only for compliance with .NET coding standards // but the disposing argument is never used, because in all cases this class // can access and release only the single unmanaged resource it wraps. private void Dispose(bool disposing) { // exit now if this object didn't completed its constructor correctly if ( this.Handle == InvalidHandle ) return; // release the unmanaged resource // eg. CloseHandle(Handle); // finally, invalidate the handle this.Handle = InvalidHandle; } }}
Notice that, if the unmanaged resource must interact with other fields, this interaction should be taken care of inside the WinResource class, not in the nested class. The UnmanagedResourceWrapper works only as a wrapper for the handle and shouldn't contain other fields or methods, besides those shown in the above listing. The code in the WinResource class must coordinate all the resources being used, both managed and unmanaged ones, and must release all of them in its Dispose method. But if the client code omits to call the Dispose method, the destructor in the nested class will orderly release the unmanaged resource during the next garbage collection.
Let's see all the advantages of this simplified approach.
I am sure that in some cases this simplified pattern can't be used, though it always worked well in my applications. I believe that it's quite odd that this simplified approach is rarely mentioned in articles and books on this topic.
Technical matters aside, I think that another kind of consideration about the Dispose/Finalize pattern is in order.
In my opinion, it is essential to put a lot of emphasis on the fact that the Dispose/Finalize pattern should be used only when your type invokes unmanaged code that allocates unmanaged resources (including unmanaged memory) and returns an handle that you must use eventually to release the resource. If the unmanaged resource is already wrapped by a .NET object (e.g. a FileStream or a SqlConnection) or a COM object, the .NET class that uses the resource must implement IDisposable, not the finalizer. And you must implement the Finalize method only if your code assigns the handle to a class-level field. If the handle is assigned to a local variable and the unmanaged resource is released before exiting the method - possibly in the Finally section of a Try block - you don't even have to implement the IDisposable interface. One of the few exceptions to this rule is when your managed code allocates unmanaged memory directly, by means of the System.Runtime.InteropServices.Marshal type.
I talked to many developers who believe that, in doubt, they should always implement the Finalize method, just in case. This is a common mistake. Defining a finalizable class without a real reason to do so can hurt performance, because the CLR takes slightly longer to allocate finalizable objects, because it has to register them in the f-reachable queue. And in the worst case - that is if the caller omits to call to the Dispose method - a finalizable object can have even more impact on performance, because it will be promoted to a higher generation without any real reason for such an overhead.
To recap: when do you really need to implement the Finalize method? Thinking of all the commercial apps I worked on in these years, I'd say that I used this pattern no more than 4 or 5 times. For sure, I used it more frequently in books and articles than in the real world.
Remember Me
Powered by: newtelligence dasBlog 1.8.5223.1