Click here to Skip to main content
Click here to Skip to main content

IDisposable: What Your Mother Never Told You About Resource Deallocation

, 29 Sep 2008
Rate this:
Please Sign up or sign in to vote.
One difficulty of the IDisposable interface overcome with the Disposable Design Principle.

Introduction - Recommended Reading

A lot of the information regarding .NET internals in this article was gleaned from "CLR via C#", 2nd Ed., by Jeffrey Richter of Wintellect, Microsoft Press. If you don't already own it, buy it. Now. Seriously. It's an essential resource for any C# programmer.

The Boring Stuff - IDisposable Gone Awry

This article is divided into two parts: the first part is an evaluation of many of the problems inherent with IDisposable, and the second part develops some "best practices" when writing IDisposable code.

Deterministic Resource Deallocation - Its Necessity

During more than 20 years of coding, I have occasionally found it necessary to design my own small programming languages for certain tasks. These have varied from imperative scripting languages to a specialized regex for trees. There are many choices to make in language design: a few simple rules should never be broken, and there are many general guidelines. One of these guidelines is:

Never design a language with exceptions that do not also have deterministic resource deallocation.

Guess which guideline the .NET runtime (and, by extension, every .NET language) does not follow? This article will examine some of the consequences of this choice, and then propose some best practices to try to work with this situation.

The reason for the guideline is that deterministic resource deallocation is necessary to produce usable programs with maintainable code. Deterministic resource deallocation provides a certain point at which a coder may know that the resource has been deallocated. There are two approaches to writing reliable software: the traditional approach deallocates resources as soon as possible, while a modern approach lets resource deallocation occur at an indeterminate time. The advantage to the modern approach is that programmers do not have to deallocate resources explicitly. The disadvantage is that it becomes much more difficult to write reliable software; an entire new set of difficult-to-test error conditions is added to the allocation logic. Unfortunately, the .NET runtime was designed around the modern approach.

The .NET runtime's support for indeterministic resource deallocation is through the Finalize method, which has special meaning to the runtime. Microsoft has also recognized the need for deterministic resource deallocation, and has added the IDisposable interface (and other helper classes which we will look at later). However, the runtime views IDisposable as just another interface, without any special meaning; and this reduction in status to second-class citizen causes some difficulties.

In C#, a "poor man's deterministic deallocation" may be implemented through the use of try and finally, or the not-much-cleaner equivalent of using. There was a lot of debate in Microsoft whether to have reference-counted references or not, and I personally believe they made the wrong decision. As a result, deterministic resource deallocation requires the use of either the awkward finally/using, or the error-prone direct call to IDisposable.Dispose. Neither of these are particularly attractive to the programmer of reliable software who is used to, e.g., C++'s shared_ptr<T>.

The IDisposable Solution

IDisposable is Microsoft's solution for allowing deterministic resource deallocation. It is intended to be implemented in the following use cases:

  • Any type that owns managed (IDisposable) resources. Note that the containing type must own the resources, not just refer to them. One difficulty here is that it is not obvious which classes implement IDisposable, requiring programmers to constantly look up each class in the documentation. FxCop is a help in this situation, and will flag the programmer's code if they forget.
  • Any type that owns unmanaged resources.
  • Any type that owns both managed and unmanaged resources.
  • Any type that derives from an IDisposable class. I don't recommend deriving from types that own unmanaged resources; it is usually a cleaner object-oriented design to have such types as fields rather than base types.

IDisposable does provide for deterministic deallocation; however, it also comes with its own set of problems.

IDisposable's Difficulties - Usability

IDisposable objects are cumbersome to use correctly in C#. Anyone who uses an IDisposable object locally must know to wrap it in a using construct. What makes this awkward is that C# does not allow using to wrap an object that isn't IDisposable. So, for every object being used in a deterministic program, the coder must determine if using is necessary, either from continual documentation lookups, or by wrapping everything in using and removing the ones causing compilation errors.

C++ is slightly better in this regard. They support stack semantics for reference types, which has a logic equivalent to inserting a using only if necessary. C# would benefit from allowing using to wrap non-IDisposable objects.

This problem with IDisposable is an end-user problem: it can be mitigated through code analysis tools and coding conventions, although there is no perfect solution. Adding to this problem is the fact that if resources are disposed at an indeterminate time (i.e., the coder forgot using when it was required), then the code may work fine during testing, and inexplicably fail in the field.

Depending on IDisposable instead of reference-counted references also brings up the problem of ownership. When C++'s reference-counted shared_ptr<T>'s last reference goes out of scope, then its resources are disposed immediately. In contrast, IDisposable-based objects place the burden on the end-user to explicitly define which code "owns" the object, and is therefore responsible for disposing of its resources. Sometimes, ownership is obvious: when an object is owned by another object, the container object also implements IDisposable and is, in turn, disposed by its owner. In another very common case, the lifetime of an object can be defined by its code scope at some point in the program, and the end-user places a using construct to define the block of code that "owns" that object. However, there are a few other cases where the object lifetime may be shared by different code paths, and these are more difficult for the end-user to code correctly (whereas a reference-counted reference would give this problem a simple solution).

IDisposable's Difficulties - Backwards Compatibility

Adding IDisposable to or removing IDisposable from an interface or class is a breaking change. Proper design implies that the client code will primarily use interfaces, so IDisposable could be added to an internal class in that situation, bypassing the interface. However, this can still cause a problem with older client code.

Microsoft ran into this problem themselves. IEnumerator did not derive from IDisposable; however, IEnumerator<T> did. Now, when older client code expecting the IEnumerable collections is given IEnumerable<T> collections instead, their enumerators are not correctly disposed.

Is this the end of the world? Probably not, but it does open up some questions about how the second-class citizen status of IDisposable influences design choices.

IDisposable's Difficulties - Design

The single greatest drawback caused by IDisposable in the area of code design is this: every interface designed must predict whether or not their derived types will need IDisposable. The actual quote is, "Implement the Dispose design pattern on a base type that commonly has derived types that hold onto resources, even if the base type does not" (from Implementing Finalize and Dispose to Clean Up Unmanaged Resources). From a design perspective, interfaces need to predict whether implementations of that interface will need IDisposable.

If an interface does not inherit from IDisposable, yet one of its implementations needs it (e.g., an implementation from a third-party vendor), then the end-user code encounters a "slicing" problem. The end-user code does not realize that the class it is using needs to be disposed. The end-user code may cope with this problem by testing objects it is using for the IDisposable interface explicitly; however, this changes the awkward using into a truly horrible-looking finally construct for each and every abstract local object. Placing this kind of burden on the end user is, in my opinion, unacceptable (at least without language support).

This "slicing" problem is the generalization of the problem Microsoft had when updating IEnumerator; they decided that enumerators often need to free resources, so they added IDisposable to IEnumerator<T>. However, adding IDisposable to a derived type can result in slicing when the end-user code uses the old IEnumerator interface.

For some interfaces, it's rather obvious whether their derived types will need IDisposable. For other interfaces, however, it's not nearly as clear, and yet the decision must be made when the interface is published. In the general case, this is a real problem.

In short, IDisposable prevents designing reusable software. The underlying problem causing all this difficulty is the violation of one of the central principles of Object Oriented Design: keep the interface separate from the implementation. The type of resources allocated and deallocated internally by an implementation of an interface should be an implementation detail. However, when Microsoft made the decision to only support IDisposable as a second-class citizen, they were deciding to view the deallocation of resources as an interface instead of an implementation detail. They were wrong, and these difficulties are the result of violating the principle of separation.

There is one rather unattractive solution: have every interface and every class descend from IDisposable. Since IDisposable may be implemented as a noop, it really only means that the derived interface or class may have an implementation, derived class, or future version that does deallocate resources. I personally have not been brave enough to embrace this as a design guideline — yet.

The final difficulty regarding the IDisposable design is how they interact with collections. Since IDisposable is an interface, either collections must behave differently when they "own" their items, or the end-user must remember to invoke IDisposable.Dispose explicitly, when necessary. If this responsibility is placed on the collection classes, then that would imply a new set of collection classes that "own" their items; and duplicating a class hierarchy is a red flag to any designer, indicating that there is something wrong. If .NET supported reference-counted references (i.e., IDisposable as a first-class citizen), then none of this would be a problem.

IDisposable's Difficulties - Additional Error State

Another difficulty with IDisposable is that it is explicitly invokeable, and not tied to the lifetime of the object. In particular, this adds a new "disposed" state to every disposable object. With the addition of this state, Microsoft recommends that every type implementing IDisposable will check in every method and property accessor to see if it has been disposed, and throw an exception if it has. Ewww... This reminds me of a co-worker I once had who insisted that we run memory checksum algorithms every time we allocate memory, "just in case" the RAM is about to fail. In my opinion, checking for the disposed state is just a waste of cycles, and would only be useful in debug code. If end users can't follow even the most basic software contracts, they won't ever produce working code anyway.

Instead of checking for the disposed state and throwing an exception, I recommend supporting "undefined behaviour". Accessing a disposed object is the modern equivalent of accessing deallocated memory.

IDisposable's Difficulties - No Guarantees

Since IDisposable is just an interface, objects implementing IDisposable only support deterministic deallocation; they cannot require it. Since it is considered perfectly acceptable for the end-user to not dispose of an object (a convention I disagree with), any IDisposable object must support extra logic to handle indeterministic deallocation as well as deterministic deallocation. Once again, indeterministic deallocation is the standard that every .NET object must support, and deterministic deallocation is just an optional addition that cannot be enforced. True enforcement of deterministic resource deallocation would require reference-counted references.

IDisposable's Difficulties - Complexity of Implementation

Microsoft has a code pattern for implementing IDisposable. Not many coders fully understand this code (e.g. why a call to GC.KeepAlive is necessary, and why synchronization of the disposed field is not). There are several other articles that describe in detail how to implement this code pattern. The following are the reasons behind its somewhat obscure design:

  • It's possible that IDisposable.Dispose may never be called, so the disposable object must include a finalizer that disposes resources. In other words, deterministic deallocation must support indeterministic deallocation.
  • IDisposable.Dispose may be called multiple times without adverse side-effects, so any real disposal code needs to be protected by a check that it has not already run.
  • Because finalizers are run on all unreachable objects in an arbitrary order, finalizers may not access managed objects. Therefore, the resource deallocation method must be able to handle both a "normal" dispose (when called from IDisposable.Dispose) and an "unmanaged-only" dispose (when called from Object.Finalize).
  • Since finalizers run on a separate thread(s), there is the possibility that the finalizer may be called before IDisposable.Dispose returns. Judicial use of GC.KeepAlive and GC.SuppressFinalize may be used to prevent race conditions.

In addition, the following facts are often overlooked:

  • Finalizers are called if constructors throw an exception (another convention I disagree with); therefore, the disposal code must gracefully handle partially-constructed objects.
  • Implementing IDisposable on a CriticalFinalizerObject-derived type is tricky because void Dispose(bool disposing) is virtual, yet it must run within a Constrained Execution Region. This may require an explicit call to RuntimeHelpers.PrepareMethod.

The naming convention for the recommended IDisposable code pattern is confusing at best. The object is implementing the interface IDisposable, and needs a boolean field disposed. So far so good, but then: as part of the code pattern, the implementation of IDisposable.Dispose calls the overloaded Dispose with a boolean disposing parameter that indicates what type of disposing can take place (not whether disposing is taking place). The naming conventions for this code pattern are confusing, even for veteran C# programmers, if they aren't constantly reviewing the code pattern. Any deviation is flagged by FxCop as a violation of this code pattern.

This is one area where C++ again has a bit of an edge on C#. The C++ compiler does much of the work of implementing IDisposable due to its destructor syntax. C# is a pure .NET language, so in many ways, has a more natural syntax than C++. However, C++ does have two helpful advantages when it comes to deterministic resource deallocation: destructor syntax, and stack semantics for reference types.

Even for the perfect programmer, the complexity of the recommended IDisposable code pattern increases the possibility of incorrect code written in libraries or by coworkers. It is a natural impulse to place shutdown logic within a Dispose method. However, since we cannot touch managed objects when in finalizers, and since IDisposable only supports deterministic deallocation rather than enforcing it, this is often a mistake. The recommended IDisposable code pattern can only be used to deallocate resources; it cannot be used to support general shutdown logic. This fact is all too often forgotten.

Sometimes, for simplicity or by accident, IDisposable is just forgotten. FxCop catches some of these violations (such as the common case where an object contains an IDisposable object), but it misses other cases. Microsoft coders themselves have fallen into this trap: WeakReference does not implement IDisposable, and it certainly should. Programmers who do not believe in the necessity of deterministic resource deallocation may simply ignore IDisposable altogether.

IDisposable's Difficulties - Impossibility of Shutdown Logic (Managed Finalization)

Shutdown logic is a common need in any real-world application. This is particularly true in asynchronous programming models. For example, a class that has its own child thread would wish to stop that thread by setting a ManualResetEvent. While it is perfectly reasonable and expected to do this when IDisposable.Dispose is called directly, this would be disastrous if called from a finalizer. Since IDisposable does not enforce deterministic deallocation, there is not even a warning to the end-user who forgets or neglects to call IDisposable.Dispose; their program just slowly leaks resources. This raises the question: is there any way for the finalizer code to access managed objects?

In order to better understand the restrictions on finalizers, we must understand the garbage collector. [Note: The description of garbage collection and finalization given here is a simplification; the actual implementation is considerably more complicated. Generations, resurrection, weak references, and several other topics are ignored. For the purpose of this article, however, this logical description is correct and reasonably complete.]

The .NET garbage collector utilizes a mark/sweep algorithm. Specifically, it does the logical equivalent of the following:

  1. Suspends all threads (except the finalizer thread(s)).
  2. Creates a set of "root" objects. If the AppDomain is unloading or the CLR is shutting down, then there are no root objects. For normal garbage collections, the root objects are:
    • Static fields.
    • Method parameters and local variables for the whole call stack of each thread, unless the current CLI instruction has already moved past the point of their last access (e.g. if a local variable is only used for the first half of a function, then it is eligible for garbage collection for the second half of the function) - note that the this pointer is included here.
    • Normal and pinned GCHandle table entries (these are used for Interop code, so the GC doesn't remove objects that are referenced only by unmanaged code).
  3. Recursively marks each root object as "reachable": for each reference field in the reachable object, the object referred to by the field is recursively marked as reachable (if it isn't already).
  4. Identifies the remaining, unmarked objects as "unreachable".
  5. Recursively marks each unreachable object with a finalizer (that hasn't had GC.SuppressFinalize called on it) as reachable, and places them on the "finalization reachable queue" in a mostly-unpredictable order.

In parallel with the garbage collection above, finalization is also constantly running in the background:

  1. A finalizer thread takes an object from the finalization reachable queue, and executes its finalizer - note that multiple finalizer threads may be executing finalizers for different objects at any given time.
  2. The object is then ignored; if it is still reachable from another object on the finalization reachable queue, then it will be kept in memory; otherwise, it will be considered unreachable, and will be collected at the next garbage collection sweep.

The reason that finalizers may not access managed objects is because they do not know what other finalizers have been run. Any object that has fields to other objects may access those fields, since the other objects will still be in memory, but nothing may be done with them, since they may have already had their finalizers run (thus disposing them). Even calling Dispose would be an error, since Dispose may already be running in the context of another finalizer thread. Calling Dispose would be meaningless anyway, since those objects are either reachable from live code, or are already on the finalization reachable queue. Also note that in the case of the AppDomain unloading or the CLR shutting down, all objects become eligible for garbage collection, including CLR runtime support objects and static reference fields; not even static methods such as EventLog.WriteEntry may be called in this situation.

There are a handful of exceptions where finalizers may access managed objects:

  • The finalization reachable queue is partially ordered: finalizers for CriticalFinalizerObject-derived types are called after finalizers for non-CriticalFinalizerObject-derived types. This means that, e.g. a class with a child thread may call ManualResetEvent.Set for a contained ManualResetEvent, as long as the class does not derive from CriticalFinalizerObject.
  • The Console object and some methods on the Thread object are given special consideration. This explains why example programs can create an object calling Console.WriteLine in its finalizer and then exit, but the same program won't work with EventLog.WriteEntry.

Generally speaking, finalizers may not access managed objects. However, support for shutdown logic is necessary for reasonably-complex software. The Windows.Forms namespace handles this with Application.Exit, which initiates an orderly shutdown. When designing library components, it is helpful to have a way of supporting shutdown logic integrated with the existing logically-similar IDisposable (this avoids having to define an IShutdownable interface without any built-in language support). This is usually done by supporting orderly shutdown when IDisposable.Dispose is invoked, and an abortive shutdown when it is not. It would be even better if the finalizer could be used to do an orderly shutdown whenever possible.

Microsoft came up against this problem, too. The StreamWriter class owns a Stream object; StreamWriter.Close will flush its buffers and then call Stream.Close. However, if a StreamWriter was not closed, its finalizer cannot flush its buffers. Microsoft "solved" this problem by not giving StreamWriter a finalizer, hoping that programmers will notice the missing data and deduce their error. This is a perfect example of the need for shutdown logic.

A Brief Hiatus - Where We Are

"But, of course, this is all far too messy. It runs counter to the goals for our new managed platform to force developers to worry about this sort of thing." (Chris Brumme, "Lifetime, GC.KeepAlive, handle recycling", blog post 2003-04-19)

<sarcasm strength="mild">Ahh, yes. .NET sure has made it easy. Why, unmanaged destructors in C++ are so much more complicated than all of this.</sarcasm> Seriously, this article could be much longer if it included the really complex issues such as resurrection and the restrictions on finalizers that descend from CriticalFinalizerObject.

I'd like to take a moment to sing some praises of .NET and C#. Although I do disagree with a couple of Microsoft's decisions, on the whole, they've done an excellent job. I'm a huge fan of any language that brings the procedural and functional families together in a more synergetic union, and C# does an excellent job of that. The .NET Framework and runtime have a few rough corners, but overall, they're better than what has come before, and they're obviously the way things are going. So far, I've been pointing out the problems caused by IDisposable, and from this point forward, I'll start looking at solving a couple of these problems.

The fact is, IDisposable is now built-in to .NET languages (though not the runtime), and any solution needs to make use of this interface. We're stuck with it, so to speak, so let's make the best of it.

The (Hopefully) Not-So-Boring Stuff - IDisposable Regulated

The first part of this article discussed the difficulties of IDisposable; this part will look at some "best practices" when writing IDisposable code.

Solving IDisposable's Difficulties - Minimize Use Cases of IDisposable by Utilizing the Disposable Design Principle

One reason for the complexity of Microsoft's recommended IDisposable code pattern is because they try to cover too many use cases. A bit of discipline in the design of IDisposable classes will go a long way:

  • For each unmanaged resource, create exactly one (possibly internal) IDisposable class that is responsible for freeing it. Microsoft followed this principle thoroughly in the BCL implementation. Note that a wrapper type for an unmanaged resource is considered a managed resource.
  • Never derive from an unmanaged resource wrapper type.
  • Create other managed IDisposable types that either own managed resources and/or derive from a type that owns managed resources.
  • Under no circumstances create a type that has to consider both managed and unmanaged resources, when implementing IDisposable. This greatly simplifies the implementation, reducing possible errors.

The Disposable Design Principle is built on these ideas:

  • Level 0 types directly wrap unmanaged resources. These types are generally sealed.
  • Level 1 types are types that derive from Level 1 types and/or contain field members that are Level 0 or Level 1 types.

To expound on this design principle, the small private or internal Level 0 classes that wrap unmanaged resources should be as close to the native API as possible, and should only concern themselves with disposing the resource correctly. All other APIs should be provided in a Level 1 class that has a Level 0 field member. This would result in two loosely-related classes (or class hierarchies): one is only responsible for wrapping the unmanaged resource, and the other only has to refer to a managed resource. This reduces our use cases for IDisposable to only two:

  1. Level 0 types: only deal with unmanaged resources.
  2. Level 1 types: only deal with managed resources (defined by a base type and/or in fields).

Implementing IDisposable on Level 1 types is rather simple: just implement IDisposable.Dispose as calling Dispose on any IDisposable field, and then, if this type is derived from an IDisposable type, call base.Dispose. This is not the place for general shutdown logic. Note the following for this simple implementation:

  • Dispose is safe to be called multiple times because it is safe to call IDisposable.Dispose multiple times, and that's all it does.
  • Level 1 type should not have finalizers; they wouldn't be able to do anything anyway, since managed code cannot be accessed.
  • It is not necessary to call GC.KeepAlive(this) at the end of Dispose. Even though it is possible for the garbage collector to collect this object while Dispose is still running, this is not dangerous since all the resources being disposed are managed, and neither this type nor any derived types have finalizers.
  • Calling GC.SuppressFinalize(this) is likewise unnecessary because neither this type nor any derived types have finalizers.

However, IDisposable is still difficult to implement correctly for the first use case. Due to the complexities of properly implementing IDisposable for unmanaged resources, it's actually best if we don't implement it altogether. This can be accomplished through the diligent use of base types that handle the common logic, or through the use of helper classes that often remove the need for IDisposable.

Solving IDisposable's Difficulties - Helper Classes for Avoiding Implementing IDisposable Directly

It is very common to have to write an unmanaged resource wrapper class for an unmanaged resource that is a pointer to some data structure. For this common use case, a higher-level abstraction is available through Microsoft-provided helper classes. System.Runtime.InteropServices.SafeHandle, System.Runtime.InteropServices.CriticalHandle, and the classes in Microsoft.Win32.SafeHandles allow writing very simple unmanaged resource wrappers if the unmanaged resource may be treated as an IntPtr. However, these are not supported on the .NET Compact Framework; on that platform, I recommend writing your own version of these extremely useful classes.

Level 0 types, in the Disposable Design Principle, should always derive from SafeHandle, if it is available on the target platform. SafeHandle and its derived classes have special P/Invoke support, which helps prevent leaking resources in some rare situations. Interop code should define function parameters, and return types as SafeHandle (or derived types) rather than IntPtr. The CriticalHandle class, in spite of the name, is actually less safe to use than SafeHandle, and should generally be avoided.

The relationship between SafeWaitHandle and WaitHandle is a perfect example of the Disposable Design Principle: SafeWaitHandle is the Level 0 class, and WaitHandle is the Level 1 class that provides the normal end-user API. SafeWaitHandle is in the SafeHandle hierarchy, implementing SafeHandle.ReleaseHandle as a call to the Win32 CloseHandle function; it only concerns itself with how to free the resource. The Level 1 WaitHandle class, in contrast, is not in the SafeHandle hierarchy; and its hierarchy exposes a full API for waitable handles, such as WaitOne.

This means there are four possibilities when having to write a new unmanaged resource wrapper (in the order of ease of implementation):

  1. There is already a Level 0 type for the unmanaged resource. In other words, the unmanaged resource is a pointer type that is already covered by a class derived from SafeHandle. Microsoft has supplied several classes already, including SafeFileHandle, SafePipeHandle, and SafeWaitHandle, among others. In this case, the programmer only needs to create a new Level 1 type.
  2. The unmanaged resource is a pointer type, but doesn't have a suitable Level 0 type already defined. In this case, the programmer needs to create two classes, one Level 0 and one Level 1.
  3. The unmanaged resource that needs wrapping is a simple pointer type along with some additional information (such as a secondary pointer or integral "context" value). In this case, the programmer must also create two classes, but the implementation details of the Level 0 type are more complex.
  4. The unmanaged resource is not a pointer type at all. In this case, the programmer must create two classes, and the implementation details of both are much more complex.

Note that when creating hierarchies of Level 1 types, it is common practice to declare a protected property in the (possibly abstract) base Level 1 type, and this field should have the type and name of the related Level 0 type. For example, the Level 1 abstract base type WaitHandle establishes the Level 1 hierarchy for waitable handles, and it has a protected property named SafeWaitHandle of type SafeWaitHandle.

Wrapping Unmanaged Resources - Using Existing Level 0 Types (The Easy Case)

To define a new Level 1 type that uses a Level 0 type, extend an existing Level 1 hierarchy, if possible.

The example for using existing Level 0 (SafeHandle-derived) types is ManualResetTimer (named to match the existing ManualResetEvent). Of the many timers provided by the .NET framework, they did not include a WaitHandle-based timer that gets signalled when the timer goes off. This "Waitable Timer", as it is called by the SDK, is commonly used by asynchronous programs. For simplicity, this sample does not support periodic timers or timers with asynchronous callback functions.

Note that ManualResetTimer derives from WaitHandle (the Level 1 hierarchy) because the Level 0 SafeWaitHandle already correctly disposes of the unmanaged resource. Because of the Level 0/Level 1 class hierarchy division already in place, implementing ManualResetTimer is quite straightforward.

[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("kernel32.dll", EntryPoint = "CreateWaitableTimer", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern SafeWaitHandle DoCreateWaitableTimer(IntPtr lpTimerAttributes,
        [MarshalAs(UnmanagedType.Bool)] bool bManualReset, string lpTimerName);
    internal static SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, 
             bool bManualReset, string lpTimerName)
    {
        SafeWaitHandle ret = DoCreateWaitableTimer(lpTimerAttributes, 
                             bManualReset, lpTimerName);
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("kernel32.dll", EntryPoint = "CancelWaitableTimer", 
               SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DoCancelWaitableTimer(SafeWaitHandle hTimer);
    internal static void CancelWaitableTimer(SafeWaitHandle hTimer)
    {
        if (!DoCancelWaitableTimer(hTimer))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }

    [DllImport("kernel32.dll", EntryPoint = "SetWaitableTimer", 
               SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool DoSetWaitableTimer(SafeWaitHandle hTimer, 
            [In] ref long pDueTime, int lPeriod,
            IntPtr pfnCompletionRoutine, IntPtr lpArgToCompletionRoutine, 
            [MarshalAs(UnmanagedType.Bool)] bool fResume);
    internal static void SetWaitableTimer(SafeWaitHandle hTimer, long pDueTime, 
             int lPeriod, IntPtr pfnCompletionRoutine,
             IntPtr lpArgToCompletionRoutine, bool fResume)
    {
        if (!DoSetWaitableTimer(hTimer, ref pDueTime, lPeriod, 
                 pfnCompletionRoutine, lpArgToCompletionRoutine, fResume))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }
}

/// <span class="code-SummaryComment"><summary></span>
/// A manual-reset, non-periodic, waitable timer.
/// <span class="code-SummaryComment"></summary></span>
public sealed class ManualResetTimer : WaitHandle
{
    /// <span class="code-SummaryComment"><summary></span>
    /// Creates a new <span class="code-SummaryComment"><see cref="ManualResetTimer"/>.</span>
    /// <span class="code-SummaryComment"></summary></span>
    public ManualResetTimer()
    {
        SafeWaitHandle = 
          NativeMethods.CreateWaitableTimer(IntPtr.Zero, true, null);
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Cancels the timer. This does not change the signalled state.
    /// <span class="code-SummaryComment"></summary></span>
    public void Cancel()
    {
        NativeMethods.CancelWaitableTimer(SafeWaitHandle);
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Sets the timer to signal at the specified time,
    /// which may be an absolute time or a relative (negative) time.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="dueTime">The time, interpreted</span>
    /// as a <span class="code-SummaryComment"><see cref="FILETIME"/> value</param></span>
    private void Set(long dueTime)
    {
        NativeMethods.SetWaitableTimer(SafeWaitHandle, dueTime, 0, 
                                       IntPtr.Zero, IntPtr.Zero, false);
    }

    /// <span class="code-SummaryComment"><summary></span>
    /// Sets the timer to signal at the specified time. Resets the signalled state.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="when">The time that this</span>
    /// timer should become signaled.<span class="code-SummaryComment"></param></span>
    public void Set(DateTime when) { Set(when.ToFileTimeUtc()); }

    /// <span class="code-SummaryComment"><summary></span>
    /// Sets the timer to signal after a time span. Resets the signaled state.
    /// <span class="code-SummaryComment"></summary></span>
    /// <span class="code-SummaryComment"><param name="when">The time span after</span>
    /// which the timer will become signaled.<span class="code-SummaryComment"></param></span>
    public void Set(TimeSpan when) { Set(-when.Ticks); }
}

Note the following:

  • Always use SafeHandle or derived types as parameters and return values for interop functions. For example, this sample code uses SafeWaitHandle instead of IntPtr. This prevents resource leaks if a thread is unexpectedly aborted.
  • Since a Level 1 hierarchy is already in place, ManualResetTimer doesn't have to deal with disposing, even of its managed resources. This is all handled by the WaitHandle base type.

Wrapping Unmanaged Resources - Defining Level 0 Types for Pointers (The Intermediate Case)

There are many cases where a suitable Level 0 type doesn't exist. These situations require defining a Level 0 type and then defining a Level 1 type (or type hierarchy). Defining Level 0 types is more complicated than defining Level 1 types.

The example for defining simple Level 0 types is a window station object. This is one of the many resources that is represented by a single IntPtr handle. First, the Level 0 type must be defined:

[SecurityPermission(SecurityAction.LinkDemand, 
   Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("user32.dll", EntryPoint = "CloseWindowStation", 
      SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool CloseWindowStation(IntPtr hWinSta);
}

/// <span class="code-SummaryComment"><summary></span>
/// Level 0 type for window station handles.
/// <span class="code-SummaryComment"></summary></span>
public sealed class SafeWindowStationHandle : SafeHandle
{
    public SafeWindowStationHandle() : base(IntPtr.Zero, true) { }
    public override bool IsInvalid
    {
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [PrePrepareMethod]
        get { return (handle == IntPtr.Zero); }
    }

    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
    [PrePrepareMethod]
    protected override bool ReleaseHandle()
    {
        return NativeMethods.CloseWindowStation(handle);
    }
}

Notes on the code:

  • The unmanaged resource deallocation function (in this case, NativeMethods.CloseWindowStation) does take a regular IntPtr (not a SafeWindowStationHandle) to deallocate the resource.
  • Since SafeHandle derives from CriticalFinalizerObject, both IsInvalid and ReleaseHandle may be run in a Constrained Execution Region, meaning:
    • They cannot allocate objects, box values, acquire locks, or call methods through delegates, function pointers, or Reflection.
    • They should be decorated with a ReliabilityContractAttribute and a PrePrepareMethodAttribute.
  • Both IsInvalid and ReleaseHandle may be run from a finalizer during system shutdown, so they may not access any managed objects whatsoever.

Since a Level 0 type's ReleaseHandle only P/Invokes its resource cleanup function and returns, the Constrained Execution Region and finalizer restraints are not troublesome in practice. The only awkwardness is in the additional attributes that are necessary.

Once the Level 0 type is completed, then the Level 1 type may be defined:

[SecurityPermission(SecurityAction.LinkDemand, 
      Flags = SecurityPermissionFlag.UnmanagedCode)]
internal static partial class NativeMethods
{
    [DllImport("user32.dll", EntryPoint = "OpenWindowStation", 
        CharSet = CharSet.Auto, BestFitMapping = false,
        ThrowOnUnmappableChar = true, SetLastError = true), 
     SuppressUnmanagedCodeSecurity]
    private static extern SafeWindowStationHandle 
            DoOpenWindowStation(string lpszWinSta,
            [MarshalAs(UnmanagedType.Bool)] bool fInherit, 
            uint dwDesiredAccess);
    internal static SafeWindowStationHandle 
             OpenWindowStation(string lpszWinSta, 
             bool fInherit, uint dwDesiredAccess)
    {
        SafeWindowStationHandle ret = 
          DoOpenWindowStation(lpszWinSta, fInherit, dwDesiredAccess);
        if (ret.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return ret;
    }

    [DllImport("user32.dll", EntryPoint = "SetProcessWindowStation", 
         SetLastError = true), SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool 
            DoSetProcessWindowStation(SafeWindowStationHandle hWinSta);
    internal static void SetProcessWindowStation(SafeWindowStationHandle hWinSta)
    {
        if (!DoSetProcessWindowStation(hWinSta))
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
    }
}

/// <span class="code-SummaryComment"><summary><