There are multiple explanations for how to use IDisposable. I get it now, so here goes:
public class Connection : IDisposable { public IntPtr buffer; // Unmanaged resource (memory buffer) public SafeHandle resource; // Managed resource that implements IDisposable private bool disposed; public Connection() { buffer = ... // allocates memory resource = new SafeHandle( ... ) // allocates some managed resource that we assume allocates an unmanaged resource, but we don't care } ~ Connection() { Dispose(false); } public void StartConnection() { if (disposed) throw new ObjectDisposedException(); ... // implementation } public void Dispose() { // NOTE: If you think multiple threads could try to Dispose your object // consider locking this section as well. DON'T do this in the Dispose(bool) // function, as locks are unsafe at finalization time (and there is only one // finalizer thread) Dispose(true); GC.SuppressFinalize(this); } public virtual void Dispose(bool disposing) { // No need to repeat the frees if (!disposed) { // Release ALL unmanaged resources here so that it's ALWAYS done! Release(buffer); if (disposing) { // If the caller has explicitly called the Dispose() method, call Dispose() on managed objects early. // By early, I mean that we TRUST disposable managed objects to do the same thing we are above for UNMANAGED resources resource.Dispose(); // Note that we don't have to check if it's NULL first because this will never execute in the Finalized state } } } }
The complex bit here is the trust that all IDisposable objects behave like this. Basically, an object should never let itself leak an unmanaged resource. Thus, it frees unmanaged resources both when Dispose() is called and on from the Finalizer.