CLR Inside Out
Digging into IDisposable
Shawn Farkas
Contents
Disposable Objects
The Disposable Pattern
Managed vs. Native Resources
Managed Resource Cleanup
Deriving from a Disposable Type
Dispose and Security
SafeHandles
Conclusion
One of the major productivity benefits that the common language runtime (CLR) offers developers of managed code is that the garbage collector (GC) makes sure any memory allocated on the managed heap is cleaned up after it is no longer needed. Developers are thus saved countless hours of debugging difficult problems arising from memory leaks, from trying to use released memory, and from double-freeing memory. However, while the garbage collector is great at making sure that memory does not leak, it doesn’t have any knowledge about other resources that need to be freed. For instance, the garbage collector doesn’t know how to close a file handle or how to release memory allocated outside of the managed heap with an API such as CoAllocTaskMem.
Objects that manage these types of resources must ensure that they are released when they’re no longer needed. You can accomplish this by overriding the Finalize method of System.Object, which lets the garbage collector know that the object would like to participate in its own cleanup (in C# you use the C++ destructor syntax, ~MyObject, rather than directly overriding the method). If a class has a finalizer, then before objects of that type are collected the garbage collector will call the object’s finalizer and allow it to clean up any resources that it may be holding onto.
One problem with this system is that the garbage collector does not run deterministically and, as a result, your object may not be finalized for a long time after the last reference to it has gone away. If your object holds onto an expensive or rare resource, such as a database connection, this may not be acceptable. For instance, if your object has 1 of only 10 available connections open, it should release that connection as soon as possible, rather than waiting for the garbage collector to call the finalize method.
Disposable Objects
To avoid having to wait an indeterminate amount of time for the garbage collector to act, types that own resources should implement the IDisposable interface; then consumers of that type can release those resources in a timely manner. Implementing IDisposable is a hint to consumers of your object that they should call Dispose as soon as they are finished with it, which enables the object to release resources as soon as they are no longer needed. In fact, C# and Visual Basic® both provide a "using" keyword to make this process as painless as possible. In C#, if you have a class, MyClass, which implements IDisposable, then the following code will cause the compiler to generate code similar to that in Figure 1.
Figure 1 Generated Code
MyClass myClass = GetMyClass();
try
{
myClass.DoSomething();
}
finally
{
IDisposable disposable = myClass as IDisposable;
if (disposable != null) disposable.Dispose();
}
using (MyClass myClass = GetMyClass())
{
myClass.DoSomething();
}
The try/finally block ensures that even if the code within the using block throws an exception, MyClass’s Dispose method will be given an opportunity to clean up the object. The compiler-generated code does two interesting things before calling the Dispose method. First, it checks to ensure that the disposable object is not null—so if the expression assigned to the variable happens to evaluate to null, a using block will not cause a NullReferenceException in your app. It also calls Dispose through an IDisposable reference, which allows types that explicitly implement IDisposable to still be cleaned up properly.
Although the using block will work with classes that do have an explicit IDisposable implementation, I recommend that classes never implement the interface this way. If you explicitly implement IDisposable, developers who are exploring your object model using IntelliSense® in Visual Studio® will not notice that the object has a Dispose method and therefore may not take advantage of early cleanup, causing your object to hold onto its resources until the garbage collector runs the finalizer.
It’s important to note that from the garbage collector’s point of view, IDisposable is just another interface. The garbage collector does not treat disposable objects differently from objects that do not have an IDisposable implementation; specifically, the garbage collector will never call your Dispose method for you. The only cleanup method that the garbage collector will call is the object’s finalizer; if code is not written to explicitly call the Dispose method, then the method will never be called.
The Disposable Pattern
You’ve seen that the "using" constructs in C# and Visual Basic make cleaning up disposable objects easy. On the other side of that equation are developers who need to create disposable types. Since the IDisposable interface defines only a single method, it seems as though developers of disposable types should be able to quickly and easily implement the interface. However, as indicated in Joe Duffy’s IDisposable guidelines, there are many subtle details to implementing a truly robust disposable type.
One key observation about a Dispose method is that the cleanup work it does is very similar to the cleanup work that would be done by the object’s finalizer. Since avoiding code duplication is always a good thing, it’s better to share this cleanup code between the two locations rather than to copy and paste it between them. This also has the benefit of keeping all of the type’s cleanup code in one location. The obvious way to share this code is to put all of the cleanup code into the Dispose method and simply have the object’s finalizer call into Dispose:
public class MyClass : IDisposable
{
~MyClass()
{
Dispose();
}
public void Dispose()
{
// Cleanup
}
}
Though this certainly accomplishes the goal of keeping all of the cleanup code in one place, there is a problem with implementing a finalizer this way. While some of the cleanup code may be shared between Dispose and the finalizer, there is actually a set of resources that you would want to release in a Dispose method, but you would not want to release during finalization.
Managed vs. Native Resources
Since finalizers will not run in a deterministic order, you cannot guarantee that objects your object references have not already been finalized when your finalizer is run. For example, consider the following classes:
public class Database : IDisposable
{
private NetworkConnection connection;
private IntPtr fileHandle;
// ...
}
public class NetworkConnection : IDisposable
{
private IntPtr connectionHandle;
// ...
}
The Database class contains two resources, one of type NetworkConnection and an IntPtr, which represents a file handle. While there is still an active reference to an object of type Database, the garbage collector will not collect the NetworkConnection object referenced by the connection member variable. However, if the garbage collector is calling the Database object’s finalizer, then you know that there were no more live references to the object and it became eligible for collection.
It’s possible that prior to the execution of the database finalizer, the NetworkConnection object may have already been finalized if there were no other live references to it. Using an object that has already been finalized is a bad idea as the object will likely not be in a usable state; since there are no guarantees as to the order of finalization, the Database finalizer should not reference the connection field.
However, if you’re cleaning up as a result of a call to the Dispose method, you know that you are not being finalized since there must have been a live reference to the object for Dispose to have been called. Your Database object is still alive and it is acting as a GC root for the NetworkConnection referenced by the connection field, preventing the NetworkConnection object from being finalized. Therefore, in a Dispose method it is safe to access finalizable member variables.
The story is different with the fileHandle field, however. This handle field is just an IntPtr, which is not something that the garbage collector will finalize. This means it is safe to access the fileHandle field in both the finalizer and the Dispose method.
Generalizing this principle, in a Dispose method it’s safe to clean up all resources that an object is holding onto, whether they are managed objects or native resources. However, in a finalizer it is only safe to clean up objects that are not finalizable, and generally the finalizer should only be releasing native resources.
It’s interesting to notice that in this example the connection field is not considered a native resource even though it clearly contains a native connection handle. While the internal implementation of the NetworkConnection class does contain a native resource, from the perspective of the Database class NetworkConnection is a managed resource since the NetworkConnection type is responsible for managing the lifetime of its own resources. Within the NetworkConnection class, however, connectionHandle is a native resource, as it will not be cleaned up unless code is written to explicitly do so.
Given that you want to clean up all of your resources if you’re being disposed and a subset of these resources if you’re being finalized, you can factor your cleanup code into a method that is called by both the finalizer and Dispose method. This new method will then make sure that all resources are cleaned up if you are disposing and that only the native resources are cleaned up if the object is being finalized. This requires you to create a disposable implementation that follows the pattern in Figure 2.
Figure 2 Basic IDisposable Pattern
public class DisposableClass : IDisposable
{
~DisposableClass()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Clean up all managed resources
}
// Clean up all native resources
}
}
You’re still meeting the original goal of avoiding code duplication and keeping cleanup code together, as all of the cleanup code is now in an override of the Dispose method. I’ll refer to this method as the cleanup method for the rest of this article in order to avoid confusion with the other Dispose method. The cleanup method takes a Boolean parameter indicating if you are in the dispose code path or the finalization code path. In both code paths you should make sure to clean up the native resources your class holds onto. Additionally, in the Dispose code path you should also clean up managed resources.
The cleanup method does not need to be public, since you don’t want consumers of your class to call it directly. Instead, they can access your cleanup logic via the IDisposable interface. While consumers of your class will not need to directly invoke your cleanup method, any subclasses will want to call it, so it is marked as protected rather than private (more on subclassing disposable types later).
Since Dispose will release all resources that an object holds onto, there’s no reason for the garbage collector to finalize the object. To tell the garbage collector that the object does not need to be finalized, even though it has a finalizer, call GC.SuppressFinalize after the cleanup code has executed in your Dispose method.
In fact, if a type does not own any native resources, the code executed by the finalizer will not do anything at all. In those cases, the class should not define a finalizer method.
In the Database example, the new cleanup method might look like Figure 3. If the Database object is being disposed, it cleans up both the connection managed resource and the fileHandle native resource. However, if the object is being finalized, it cleans up only the fileHandle resource. Notice that the cleanup code is safe to call multiple times. The first call through the Dispose method will release any resources the Database object is holding onto; future calls to Dispose will find that the resources are already released and exit without attempting to free them. Dispose implementations should be coded so they will succeed even if they are called many times, even though the extra calls are unnecessary. Other methods on an object may throw an ObjectDisposedException if they are called after the object is disposed.
Figure 3 Example Database Cleanup Method
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (connection != null)
connection.Dispose();
}
if (fileHandle != IntPtr.Zero)
{
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}
It’s also interesting to notice that the cleanup code does not throw any exceptions. Generally developers expect to be able to clean up an object without causing an exception. This is because there’s really no good way to recover from an exception during a cleanup code path. If an object fails while cleaning up, the only choices a consumer of that object has is to retry and hope the second attempt succeeds, or to give up and allow that some resources may not be released properly.
In order to ensure that your cleanup code does not throw an exception, you should be sure to check any object references you access in the cleanup method to ensure that they are not null, and to only call methods that do not fail. This becomes vitally important when the object is being finalized. While an exception during a Dispose call simply leaves developers in a bad place, an unhandled exception on the finalizer thread by default tears down the entire process in version 2.0 of the CLR.
Even if you’re not running in the finalizer thread, throwing an exception during cleanup can prevent other resources from being cleaned up as you would expect. For example, take a look at Figure 4. Here, DisposableClass contains resources of type Foo and Bar. If Foo.Dispose throws an exception while executing, this DisposableClass will never get a chance to dispose of the Bar object.
Figure 4 The Dangers of Exceptions in Dispose
public class Disposableclass : IDisposable
{
Foo a;
Bar b;
// ...
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (a != null) a.Dispose();
if (b != null) b.Dispose();
}
}
}
Managed Resource Cleanup
So, if managed resources are not cleaned up when an object is being finalized, what prevents them from leaking? Well, these objects should follow the IDisposable pattern if they contain resources that need to be cleaned up. Since an object’s finalizer cannot tell if its managed resources have been finalized yet, it cannot clean them directly. However, if these types contain resources that need to be released, they should also have finalizers that the garbage collector can run; if they only contain managed resources, then no finalizer is necessary.
Let’s look at an example of this. Imagine a Cookie class used by a managed control to maintain its state across browser sessions. The Cookie class might be implemented by using IsolatedStorage, which itself might be implemented using a FileStream. The FileStream may contain a Win32® file handle, so the final classes look like the code shown in Figure 5.
Figure 5 Implementing IDisposable in a Class Hierarchy
public class FileStream : IDisposable
{
private IntPtr fileHandle;
// ...
protected void Dispose(bool disposing)
{
if (fileHandle != IntPtr.Zero)
{
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}
}
public class IsolatedStorageFileStream : IDisposable
{
private FileStream file;
// ...
protected void Dispose(bool disposing)
{
if (disposing)
{
if (file != null)
file.Dispose();
}
}
}
public class Cookie : IDisposable
{
private IsolatedStorageFileStream isolatedStore;
// ...
protected void Dispose(bool disposing)
{
if (disposing)
{
if (isolatedStore != null)
isolatedStore.Dispose();
}
}
}
If the control forgets to dispose of its Cookie object, the Cookie will not have a finalizer to clean up its IsolatedStorageFileStream, since this stream is a managed resource. That’s fine, because the garbage collector will collect the stream object for you. Again, IsolatedStorageFileStream does not need a finalizer because it only contains managed resources. This is still fine since the FileStream object will contain a finalizer, as it does own a native resource. When the garbage collector finalizes the FileStream, it will close the handle. Even though nobody disposed of the root object in this object graph, no resources were leaked because every class involved followed the disposable pattern.
Deriving from a Disposable Type
When you derive from a disposable type, and that derived type does not introduce any new resources, then nothing special needs to be done. The base type IDisposable implementation will take care of cleaning up its resources and your subclass can be blissfully ignorant of the details. However, it’s also common to have a subclass that does contain new resources that need to be cleaned up. In this case, your class needs to release its resources, while making sure that the base type’s resources also get released. Do this by overriding the cleanup method, releasing your resources, and then calling the base type to clean up its resources, as in Figure 6.
Figure 6 Overriding Dispose
public class DisposableBase : IDisposable
{
~DisposableBase()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// ...
}
}
public class DisposableSubclass : DisposableBase
{
protected override void Dispose(bool disposing)
{
try
{
if (disposing)
{
// Clean up managed resources
}
// Clean up native resources
}
finally
{
base.Dispose(disposing);
}
}
}
Notice that the derived type only has to override the cleanup method, as the inherited version of the public Dispose method and finalizer do exactly what you need. The exception to this rule is if the base type does not have a finalizer because it did not own any native resources and the derived type happened to own native resources. In this case, you would have to add a finalizer to the derived type.
In the derived type, you want to make sure to clean up your resources before you call your base type’s cleanup method. This way, objects get torn down in the reverse order that they are constructed. If you called the base type’s cleanup method first, you might get into a state where the base type has torn down part of the object that you depend upon to complete your cleanup method.
Dispose and Security
One subtlety of writing cleanup code relates to the security context it can run in. All cleanup code must be able to run no matter what identity the executing thread is impersonating. Since the garbage collector runs finalizers on a dedicated thread, any impersonation active on the thread that instantiated the disposable object will not be in place when the finalizer is run. There’s also no way to ensure that between object construction and calling Dispose, the code consuming your type hasn’t reverted the thread’s impersonation, or even impersonated another identity all together.
One example of a class that handles impersonation incorrectly is the RSACryptoServiceProvider class in the Microsoft® .NET Framework 2.0. If an RSACryptoServiceProvider object creates an ephemeral key to work with, the finalizer of the object will attempt to delete the key; this key is stored in the profile of the user the thread creating the key is running as. The problem arises if an RSACryptoServiceProvider object is created while a thread is impersonating a user, and the object is not disposed. In this situation, the finalizer will run in the context of a different user than the object constructor. When the finalizer attempts to delete the key, it will cause an exception because the user the finalizer thread is running as will likely not have access to the user profile where the key was stored.
The fact that finalizers run on a separate thread also means that you can’t rely on checking permissions of the call stack in your Dispose method. If the cleanup method is being called by your object’s finalizer, there will be no code above it on the call stack and any Code Access Security checks are meaningless. (Security demands should generally be avoided in cleanup code anyway, since they could lead to an exception.)
SafeHandles
The .NET Framework 2.0 introduced the SafeHandle class to help with resource management. A SafeHandle wraps a native resource and provides several benefits over using an IntPtr to hold onto the resource. Full details of SafeHandle are outside the scope of this article, but more information can be found in Brian Grunkemeyer’s excellent post on the Base Class Library (BCL) team blog.
A strong argument can be made that with the introduction of SafeHandle, there is no longer any reason for a type not derived from SafeHandle to contain raw native resources. Instead, objects should only contain managed resources, including subclasses of SafeHandle for any native resources they use.
Following this pattern has several benefits. First, no classes other than the SafeHandles need to have finalizers, as only the SafeHandles will own native resources. Secondly, it creates a clean model where each SafeHandle owns exactly one native resource, and managed objects can then compose various SafeHandles to achieve their needs.
Conclusion
The CLR’s garbage collector frees you from having to worry about managing memory that’s allocated on the managed heap; however, you still need to clean up other types of resources. Managed classes use the IDisposable interface to allow their consumers to release potentially expensive resources before the garbage collector finalizes the object. By following the disposable pattern and being aware of issues to look out for, classes can ensure that all of their resources get cleaned up properly and that no problems will occur when the cleanup code is run either directly via a Dispose call or via the finalizer thread.
Send your questions and comments to clrinout@microsoft.com.
Shawn Farkas is a software design engineer on the CLR security team at Microsoft. He works on security policy, cryptography, and ClickOnce. See his blog at blogs.msdn.com/shawnfa.