DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.32 The Single Instance Object

Problem

You have a data type that will be used by several clients. This data type will create and hold a reference to another object that takes a long time to create; this could be a database connection or an object that is made up of many internal objects, which also must be created along with their containing object. Rather than allow this data type to be instantiated many times by many different clients, you would rather have a single object that is instantiated only one time and used by everyone.

Solution

The following two code examples illustrate the two singleton design patterns. The first design always returns the same instance of the OnlyOne class through its GetInstance method:

public sealed class OnlyOne
{
    private OnlyOne( ) {}

    private static OnlyOne theOneObject = null;

    public static OnlyOne GetInstance( )
    {
        lock (typeof(OnlyOne))
        {
            if (theOneObject == null)
            {
                OnlyOne.theOneObject = new OnlyOne( );
            }

            return (OnlyOne.theOneObject);
        }
    }

    public void Method1( ) {}
    public void Method2( ) {} 
}

The second design uses only static members to implement the singleton design pattern:

public sealed class OnlyStaticOne
{
    private OnlyStaticOne( ) {}

    // Use a static constructor to initialize the singleton
    static OnlyStaticOne( ) {}

    public static void Method1( ) {}
    public static void Method2( ) {}
}

Discussion

The singleton design pattern allows one and only one instance of a class to exist in memory at any one time. Singleton classes are useful when you need a single way of accessing a resource such as a database connection or a file on a network. Many times, manager objects are created as singletons. For example, an object pool manager most likely would be a singleton; this allows a single access point to the pool for the entire application. Several examples of the singleton class can be found in the FCL, such as the System.Diagnostics.Trace, System.Diagnostics.Debug, and System.IO.Path classes, to name a few.

The OnlyOne class implements the singleton pattern by using a static field of the same type as its containing class—the OnlyOne type, in this case—and a common access point called GetInstance. The GetInstance method is called by the client code to obtain the one and only instance of the OnlyOne class. If there are no instantiated OnlyOne classes, a new one is created and returned. Once the OnlyOne object is created for the first time by the GetInstance method, a reference to it is placed in the theOneObject static field. This field contains a reference to the only OnlyOne object running in the application. Note that because static fields do not cross application domain boundaries, a single instance of the OnlyOne object is created for each application domain in a process. On successive calls to GetInstance, the OnlyOne object referenced by theOneObject field is returned. All access to the OnlyOne object is performed on the object returned by the GetInstance method.

The default constructor for this class has its accessibility made private to prevent code from accidentally creating an instance of this class. Setting the default constructor to private and disallowing any nonprivate constructors in this class is critical to a good singleton pattern. If code were to accidentally create a second or third class of this type, your application code would then have more than one access point to a resource, or you might have more than one manager type object managing a set of objects. When setting the default constructor to private, or if there is no default constructor in your class, you should mark the class with the sealed keyword. This keyword prevents any class from inheriting from this class. If a class were to inherit from this class, and if this new class did not provide an explicit constructor, a default constructor would be provided by the compiler. This default constructor is written to automatically call the base class's default constructor. If the base class's default constructor is private, it is inaccessible to its subclasses. The compiler will catch this type of error, but it makes the code more readable and maintainable if the sealed keyword is used to mark a singleton class.

The OnlyStaticOne class implements the singleton pattern in a much different way. This class makes exclusive use of static members to allow only one access point to this class's members (the use of the word "instance" would be misleading here—static members do not operate on an actual instance of an object, but rather on the type itself). Similar to the previous singleton pattern example, this class is also marked as sealed, and it has a private default constructor.

There are advantages and disadvantages to using each of these implementations of the singleton design pattern. The advantages of using the OnlyOne style are:

  • Converting the class into a nonsingleton class can be done easily. To do this, eliminate the sealed keyword on the class, make the constructor public, and remove the GetInstance method and the theOneObject field.

  • Modifying this pattern to allow a fixed number of instances of this type of object to be instantiated is fairly easy to do. The code to do this is shown here:

    public sealed class OnlyThree
    {
        private OnlyThree( ) 
        {
            count++;
        }
            
        private static OnlyThree[] anObject = new OnlyThree[3];
        private static int count = 0;
        private static int lastObjReturned = 0;
        private const int maxInstances = 3;
    
        public static OnlyThree GetInstance( )
        {
            if (count < maxInstances)
            {
                OnlyThree.anObject[count] = new OnlyThree( );
                lastObjReturned = count;
            }
            else
            {
                if (lastObjReturned == 1)
                {
                    lastObjReturned = maxInstances;
                }
                else
                {
                    lastObjReturned--;
                }
            }
    
            return (OnlyThree.anObject[lastObjReturned -1]);
        }
    
        public void Method1( ) {}
        public void Method2( ) {}
    }
  • Subclassing and overriding/hiding members of this class are possible. Note that you must remove the sealed keyword first.

The disadvantages to this style are:

  • The code is a bit more complex than simply using all static members.

  • You must always obtain an object reference to the one instance of this class through the GetInstance method.

  • If coded incorrectly, this type of singleton could allow more than one of these objects to be created. For instance, this could happen if the default constructor's accessibility was not set to private and you forgot to write the constructor in this class.

The advantages to using the OnlyStaticOne style of the singleton class are:

  • It is easy to read and therefore easy to maintain.

  • It is easy to write.

  • It is easy to use since there is no GetInstance method to call.

  • Making the default constructor (or any other constructor) nonprivate would have little impact on the use of this class, since instantiating a class with no instance members other than an instance constructor is of little use.

The disadvantages to this style are:

  • This style may be more difficult to convert to a regular nonsingleton class if future requirements demand it.

  • A single instance of the OnlyOne object is created for each application domain in a process. If your process will be hosting more than one application domain, you should consider using the first style of singleton class.

  • Subclassing this class requires more work than the previous singleton style.

See Also

See the "sealed" keyword and "lock Statement" topic in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section