The Singleton design pattern is arguably one of the most commonly used patterns in practice today. In this article, I'll show you how to use this pattern to properly manage unmanaged and exhaustible resources when they are used from a C# environment.
To get the greatest benefit from this article, you should have a basic familiarity with the C# language as well as traditional object-oriented design techniques. It is also assumed that you are comfortable with the implementation of the Singleton pattern as it will be given only a brief treatise here. Those unfamiliar with the Singleton pattern can find a very good introduction here.
Using the Code
The Singleton project consists of two key files: ImageWrapper.cs which implements the Singleton pattern and applies it to the consumption of unmanaged or exhaustible resources, and Program.cs which is a simple driver program meant to demonstrate the use of this project. The project also includes Sunset.jpg which will serve as a simple example of an unmanaged resource.
Simply build and run the project, and you will see a console window informing you that three instances of the
ImageWrapper class have been instantiated. In actuality, what has happened is that the singleton
ImageWrapper has had three references associated with its one and only instance. This means that while three different references actually have use of the Sunset.jpg image file, only one instance of the Sunset.jpg image has actually been created in memory. This leads to a much leaner use of available memory.
The Basic Singleton Pattern
So, let's talk briefly about the Singleton pattern. The Singleton is a very simple, standard design pattern intended for the sole purpose of ensuring that one and only one instance of a given class is ever instantiated. Note that the consuming classes may obtain as many references as they wish to the said class, only that each reference is guaranteed to always point back to the same original instantiation (think static variables shared between classes).
How is this done in C#? Well, it's simple. If you want to control how consuming objects instantiate members of your class, then you have to affect that line of control...in other words, the constructor. Every class in C# is assigned a default (
public and parameterless) constructor by the compiler if no other constructor has been supplied by you. We simply need to ensure that no constructor is ever made available. But, wait you say, how do we prevent the compiler from providing a constructor if we don't supply one ourselves. Simple, we will supply our own constructor, but we'll privatize it. That way, the compiler will see that we've provided our own constructor so it won't feel the need to supply one for us, and we will have removed the only possible way for a consuming object to instantiate an instance of our class using the standard
MyClass myClass = new MyClass() methodology. Our privatized constructor looks simply like this:
The next step is to create a private, static member variable inside our class which is of the same type as our class. This static member variable will represent the one and only instance of our Singleton object.
public class MyClass
private static MyClass _myClass;
. . .
Once this is done, we simply need to provide an alternate method for a consuming class to obtain a reference to an instance of our class. This is done by simply providing a public, static accessor property called
Instance which returns an instance of our class. Now, this part gets a little tricky. This property will reference our private member variable from the previous step. When the property is accessed, we'll check to see if our member variable is
null. If it is, then we'll initialize it using our private constructor that only we can access. Once the member variable has been initialized, we'll simply return it to the calling object. Note that since the member variable is a static object, it will retain its state and will no longer need to be initialized after the first time it's been accessed.
public static MyClass Instance
if (_myClass == null)
_myClass = new MyClass();
And that's the basic Singleton pattern. Now, any class that needs to obtain a reference to
MyClass may obtain it simply by calling our static
Instance accessor and setting the result equal to its own reference to our class, like so:
MyClass referenceToMyClass = MyClass.Instance;
The advantages to this pattern are pretty similar to the advantages to using a static object. Only one instance of the object ever exists so it is shared across all consuming classes, meaning that any change to the object by one class will be reflected in all other classes currently holding a reference to the object. There can also be some performance benefits for expensive objects, as well. For example, the
Brushes class in the .NET Framework's
System.Drawing namespace uses a type of Singleton pattern to limit the times a given
Brush object may be instantiated.
Brushes, like most GDI+ objects, are typically managed wrappers around unmanaged objects, and as such may be expensive to create and maintain. To remedy this, the
Brushes class simply holds static references to all of its underlying
Brush objects. When a certain
Brush object is requested by a consuming class, it checks to see if that
Brush's reference is
null. If so, then it creates it, and returns the newly created reference. However, the next time that
Brush is requested, the
Brushes class will see that it has already created an instance of it, and will simply return the same instance again. As these particular instances of
readonly objects, this behavior is basically transparent to the user.
Applying the Singleton Pattern to Unmanaged Resource Management
Now that we know what the Singleton pattern is, let's talk about how we can use it to better manage our unamanged resources.
Drawing on the Brushes example above, let's say that we have a large object we need to access, and that for whatever reason, it's expensive to create multiple instances of it. This may be a database connection, a network socket, or in the case of our example, a relatively large resource file such as a
Bitmap. Why should we create multiple instance of the exact same
Bitmap if all each instance will be used for is to display it unmodified? Why couldn't we just wrap the
Bitmap in a class implementing the Singleton pattern, and simply serve up references to the same
Bitmap over and over again? In this example, that's exactly what we'll do.
Take a look at ImageWrapper.cs. This is simply a class which wraps a reference to a
Bitmap object which, in turn, holds a reference to Sunset.jpg. You may have noticed, however, that there seems to be a bit more going on besides the Singleton pattern. You're right, we're also applying some basic memory management techniques here as well as implementing something called the Dispose pattern. Don't worry, we'll take all of this one step at a time.
Applying Memory Management Techniques
Glancing through the code, you may have noticed a few static calls into the
GC (for Garbage Collector) class. The
GC class provides us direct access into the inner workings of the .NET Garbage Collector. A full discussion of the .NET Garbage Collector is a little beyond the scope of this article, but basically the Garbage Collector is responsible for constantly keeping track of the amount of memory in use in your system, and then reclaiming the unused memory when it decides that you have too much. Sounds like everything works great, huh? Well, almost, except for one little detail - the .NET Garbage Collector can only keep track of managed resources. Remember the discussion before about how many objects, especially GDI+ and graphics objects, are simply managed wrappers to completely unmanaged objects? Well, that means that when you instantiate a few byte reference to a
Bitmap object which points to your 32 MB, high-resolution image of the Millenium Falcon, as far as the Garbage Collector knows, you only used up just a handful of bytes in memory. Yeah, it may know that you're pointing to something off in unmanaged land, but for all it knows, it might as well be a 2K GIF, which means that when memory starts to get tight and your machine inexplicably starts bleeding into the page file, the Garbage Collector will still be convinced that you have 32MB of RAM free somewhere, and will continue to troll along happily in its own little oblivion, while your machine constantly page faults again, and again, and again.
And that's if you only have one unmanaged resource in use.
So, with that said, you can probably just about guess what the
GC.RemoveMemoryPressure(long) calls are for, can't you?
These calls inform the Garbage Collector that some large unmanaged resource has just entered or left the playing field, respectively. Their use is simple, we simply determine how large our unmanaged resource is in bytes, and then pass that information to the calls as soon as the object has been allocated or deallocated. This causes the Garbage Collector to "reserve" some space in memory, and account for it as if it were an actual managed resource leading to a much more accurate picture of what has actually been allocated. This means that the Garbage Collector can plan for and manage the memory space much more effectively than before.
The Dispose Pattern
Now, let's talk about another pattern, called the Dispose pattern. You may have noticed that the
ImageWrapper class implements an interface called
IDisposable interface is part of a simple pattern designed to ensure that all managed objects containing unmanaged resources are properly "disposed" of. Although we'll explain the highlights of the Dispose pattern briefly in this article, you can find a much more thorough treatise on the proper implementation of the Dispose pattern here. The most obvious part of the Dispose pattern is simply the implementation of the
IDisposable interface and its single member
public void Dispose()
Note that the
Dispose method calls an overloaded
Dispose(bool) method, passing in the boolean parameter of
Dispose method is a bit more complicated.
protected virtual void Dispose(bool calledFromDisposeMethod)
This method simply determines whether or not it was called by the public facing
Dispose() method. If so, then it releases any of its allocated unmanaged resources, and then calls something called
GC.SuppressFinalize(object) is a call to the Garbage Collector, which simply tells it that when this object is ready to be collected, it won't have to do any extra "clean-up" work to it by calling its Finalizer. What is a Finalizer, you ask?
A Finalizer is a little similar to a C++ destructor. It's basically a method that a class defines for itself, which allows it to do any clean up just before it goes away. The Finalizer is denoted by the class name preceded by a tilde.
The Finalizer may only be called by the .NET framework, and is not directly accessible from user code. Notice that the Finalizer also calls the
Dispose(bool) method, but that it passes the value
false. If you recall from the
Dispose(bool) code above, when we encounter a
false value, we simply release any unmanaged resources without calling the
GC.SuppressFinalize(object) method. Since the Finalizer may only be called by the .NET framework itself, specifically the Garbage Collector, the object is being Finalized when the method is called. As you may have realized by now, an object's Finalizer is called while it is being reclaimed by the Garbage Collector, or "destroyed". As similar as this sounds to a C++ destructor, there is one crucial difference that must be mentioned. .NET object lifetimes follow what's known as a non-deterministic destruction model, whereas C++ follows a deterministic destruction model. What this basically means is that if you destroy a C++ object, its destructor is called immediately, whereas destroying a .NET object (setting it to
null or letting it simply scope itself out) simply makes it eligible for collection the next time the Garbage Collector runs. In essence, this is .NET's way of saying 'although I'll try to collect your object as soon as it makes sense to do so, I can't guarantee that it'll be the absolute very next thing I do'. This means that a lot of things could happen between when you're done with the object and when the Garbage Collector actually destroys it, namely unmanaged resources hanging around and clouding up the system for far longer then they need to, and preventing other objects from taking hold of these same resources even though you may have been finished for quite some time. This is, in essence, the point to the Dispose pattern. Think of it as your way of implementing your own sort of deterministic destruction. The Dispose pattern can guarantee us that although our object may not actually be destroyed yet, we've taken great care to ensure that any unmanaged resources we were using have been released back into the wild, taking the pressure off of the system as well as freeing them up for others to use.
The Dispose pattern implies an understanding between the consuming object and the disposable object as well. Best practices tell us that anytime we consume an object which implements the Dispose pattern, we should call the object's
Dispose() method as soon as we're done. That allows the object to start trying to clean up itself as soon as possible.
The final point to note about our
ImageWrapper class is the use of reference counting. Reference counting is a simple technique used when dealing with multiple handles into the same resource. It basically allows us to keep track of how many objects are still referring to our object, and to ensure that we don't destroy ourselves, or in this case our unmanaged resource, while others may still be accessing it. Keep in mind that if we were relying completely on the Garbage Collector to destroy our objects when we're done with them, that we would not necessarily need to keep track of the number of consuming objects ourselves. The Garbage Collector would do that for us, and would be sure not to destroy an object while any other object still had a handle to it. However, since our consuming objects will be explicitly calling our
Dispose() method as they finish, we need to keep a track of the number of objects still actually using our object before we start to destroy it. Remember,
ImageWrapper is a Singleton, and therefore any operation that a consuming object performs on us will affect all others who are using us. Specifically, if one consuming class disposes of us, all of the other consuming classes' references to
ImageWrapper will be disposed as well.
The reference counting in
ImageWrapper is simple. We simply define a static integer to hold our number of active references,
private static int _referenceCount;, and incrememnt it each time an object calls our
Instance property. Then, each time an object calls our
Dispose() method, we first decrement our reference count and then check its value. If it has reached zero, then we can safely assume that no other objects are using us and that it's safe to destroy ourselves. If it hasn't reached zero, then that means that someone is still using us, so we'll simply do nothing.
Simple, right? Absolutely. Foolproof? Note quite.
What happens if an object that's consuming us forgets to call our
Dispose() method when it's done. That means that we'll never decrement the reference count for that object accordingly, and that when the last object disposes of us, we'll still have a reference count of 1, meaning that one reference will still be unaccounted for. That means that, we'll never call our
Dispose(bool) method, and we'll never release our resources. Won't this lead to a memory leak? Well, yes and no. Remember the Finalizer? When the Garbage Collector reclaims an object, it checks to see if the object is Finalizeable (i.e., it has a Finalizer). If so, then it calls the object's Finalizer, granting it one last chance to release all of the resources it may still be claiming, to make amends with the world, so to speak. The
GC.SuppressFinalize(object) method we call in our own
Dispose(bool) method simply tells the Garbage Collector 'hey, this object may have a Finalizer, but we've already taken care of it so you don't have to call it'. If we never call our own
Dispose(bool) method because of the reference count not reaching zero, we'll never call the
GC.SuppressFinalize(object). This means that when the Garbage Collector sees itself that no one else is using our object and reclaims it, that it will call the
Finalize method itself, releasing all of our resources. So, although we may hold on to resources for a little longer than is really necessary, we're not actually leaking them to the system.
In this article, we learned how to combine the Singleton pattern and some basic knowledge of the Garbage Collector to keep better track of unmanaged resources. We also learned a bit about the Dispose pattern, and how it can be used to its fullest potential. Finally, we learned how older techniques such as reference counting still have a place alongside cutting edge technologies such as .NET.
- Initially published - October 1, 2006.