Software Tales from the Real World
Saturday, February 25, 2006
  Fun With Generics
.NET 2.0's Best New Language Feature It didn't take long after the release of .NET 2.0 for me to start finding all kinds of uses for what I consider to be the best new framework / language feature - generics. As a long-time C++ developer who used templates and the STL at every opportunity, I didn't realize how much I missed parameterized types until they were put back in my toolbox. I'm going to focus here on a concrete application of .NET generics and, in the process, point out some of their features and syntactic elements. For an excellent overview of .NET generics with comparisons to their counterparts in Java and C++, see Bruce Eckel's Interview with Anders Hejlsberg.

When templates first appeared in C++, one of their first applications was the creation of a reusable singleton class. The magazine C++ Report covered this topic in a number of articles, including John Vlissides' famous article entitled, "To Kill A Singleton". I recently needed a similar capability for a hierarchy of related classes which should each be singletons. Because these classes had inheritance relationships, using the new static class capability of .NET 2.0 wasn't appropriate. In this article, we'll build up to the solution I ultimately used.

Let's start with a very simple example:

namespace FunWithGenerics
{
public class Singleton1<T>
{
   public static T Instance
   {
       get { return instance_; }
       internal set { instance_ = value; }
   }

   static Singleton1()
   {
       Type typeT = typeof(T);
       Console.WriteLine("Singleton1 created for type '{0}'", typeT.Name);
   }

   private static T instance_;
}

public class Program1
{
   static void Main(string[] args)
   {
       Singleton1<int>.Instance = 5;
       Singleton1<string>.Instance = "Hello!";

       Console.WriteLine("Singleton<int>: Type='{0}', Instance='{1}'",
           typeof(Singleton1<int>).Name, Singleton1<int>.Instance);
       Console.WriteLine("Singleton<string>: Type='{0}', Instance='{1}'",
           typeof(Singleton1<string>).Name, Singleton1<string>.Instance);
   }
}
}
In this example, our singleton doesn't really do anything to ensure a single instance - we're just showing the basic syntax of a generic. The Singleton1 class is parameterized on any .NET type (intrinsic type, e.g. int; interface; class; etc.). In this case, the Main routine initializes two of these, one parameterized on an int, and another on a string. Because Singleton1 is not creating the instance it holds, Main must set the instance before its first use. The output when this program is run is: Singleton1 created for type 'Int32' Singleton1 created for type 'String' Singleton: Type='Singleton1`1', Instance='5' Singleton: Type='Singleton1`1', Instance='Hello!' We can see from the output that the static constructor on Singleton1 is called twice - there's really a different class instance for every distinct type on which Singleton1 is instantiated. At this point, the singleton class isn't very useful. But we have successfully created a generic class. If we now want to have the singleton generic class create the instance by simply changing the singleton's class static constructor as follows:
static Singleton2()
{
   instance_ = new T();
}
we'd get the following error: error CS0304: Cannot create an instance of the variable type 'T' because it does not have the new() constraint Here is where we encounter the first significant difference between .NET generics and C++ templates. In C++, no constraints on the type of class that can be used with a template are imposed until the template is instantiated for that type. So the above code (modified to be appropriate for C++) wouldn't cause an error until an instance of the template was created for a type which didn't have an accessible default constructor. In .NET, on the other hand, the generic class is validated when it is compiled. Therefore we must tell .NET something about the types which are allowed to be used with our singleton class:
public class Singleton2<T> where T : new()
{
   public static T Instance
   {
       get { return instance_; }
   }

   static Singleton2()
   {
       instance_ = new T();
   }

   private static T instance_;
}
Here we've used the "where" constraint specifier to tell .NET that only types which have a public default constructor - i.e. can be created by new T() - are allowed to be used as type parameters to Singleton2. At this point, if we try to use string as a type parameter, we get an error because string does not have a default constructor. For another example of where a constraint is required, consider this contrived case:
public class Singleton3<T> where T : new()
{
   public static T Instance
   {
       get { return instance_; }
   }

   static Singleton3()
   {
       Object o = new T();
       instance_ = o as T;
   }

   private static T instance_;
}
Here we're assigning the new T() to an Object reference and then casting that object reference to a T using the "as" operator. If we try to compile the above code, we get this error: error CS0413: The type parameter 'T' cannot be used with the 'as' operator because it does not have a class type constraint nor a 'class' constraint The singleton class needs to know that T is a reference type - one that's legal to use with the "as" operator. The class declaration instead needs to look like this:
public class Singleton3<T> where T : class, new()
Here we've told the compiler that T has be be a class and have a publicly accessible default constructor. If we try to create a singleton on an 'int', we get this error: error CS0452: The type 'int' must be a reference type in order to use it as parameter 'T' in the generic type or method 'FunWithGenerics.Singleton3<T>' We've covered the basics. Now let's try to come up with real singleton functionality. A class which should be accessed only as a singleton can't have a public constructor - if it did, any code could create instances of the class at will. Let's define a simple class which should be accessed as a singleton:
public class OnlyWantOne4
{
   private OnlyWantOne4()
   {
   }

   public override string ToString()
   {
        return value_;
   }

   private string value_ = "OnlyWantOne4 says hello!";
}
This class has a private constructor, and we've already seen the error we get if we try to use this class with one of our current singleton classes: error CS0310: The type 'FunWithGenerics.OnlyWantOne4' must have a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'FunWithGenerics.Singleton4<T>' Here's where some of our earlier work is put to use. We can use reflection to create a T:
public class Singleton4<T> where T : class
{
   public static T Instance
   {
       get { return instance_; }
   }

   static Singleton4()
   {
       Type typeT = typeof(T);
       ConstructorInfo ci = typeT.GetConstructor(
           BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
       object o = ci.Invoke(null);
       instance_ = o as T;
   }

   private static T instance_;
}
In this version of our singleton, we've eliminated the new() constraint. Instead, we find the private constructor and use it to instantiate a T. Then we cast our instance_ variable to a T. Obviously, nothing prevents someone using our class from doing the same thing to create an OnlyWantOne4 instance. But the private constructor makes it pretty clear how it's to be used:
public class Program4
{
   static void Main(string[] args)
   {
       OnlyWantOne4 o = Singleton4<OnlyWantOne4>.Instance;

       Console.WriteLine("Singleton<OnlyWantOne4>: Type='{0}', Instance='{1}'",
           typeof(Singleton4<OnlyWantOne4>).Name, o);
   }
}
We're pretty close to a useful singleton generic class at this point. There's one refinement we can make. In the application where I needed this, my singleton class needed to do some initialization for any T on which it was instantiated. (It figured out what it needed to do based on custom attributes I put on the T classes.) To accomplish that, my singleton class had a private Initialize method:
public class Singleton5<T> where T : class
{
   public static T Instance
   {
       get { return instance_; }
   }

   static Singleton5()
   {
       Type typeT = typeof(T);
       ConstructorInfo ci = typeT.GetConstructor(
           BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
       object o = ci.Invoke(null);
       instance_ = o as T;
       instance_.Initialize();
   }

   private void Initialize()
   {
       // Do something based on what T is
   }

   private static T instance_;
}
The static constructor calls Initialize after creating the T instance. As it stands, this will yield the following error: error CS0117: 'T' does not contain a definition for 'Initialize' The compiler doesn't know that T has an Initialize method. In some ways it would seem like the private Initialize method on the singleton class is accessible, but it's not. Instead, we can change the constraint as follows:
public class Singleton5<T> where T : Singleton5<T>
Here we're requiring that any class that wants this singleton treatment be of the Singleton5<T> type - that is, derived from Singleton5<T>:
public class OnlyWantOne5 : Singleton5<OnlyWantOne5>
{
   private OnlyWantOne5()
   {
   }

   public override string ToString()
   {
       return value_;
   }

   private string value_ = "OnlyWantOne5 says hello!";
}
Here we've created a class that's to be treated as a singleton by deriving it from our singleton generic class. We can use it via:
public class Program5
{
   static void Main(string[] args)
   {
       OnlyWantOne5 o = OnlyWantOne5.Instance;

       Console.WriteLine("OnlyWantOne5: Type='{0}', Instance='{1}'",
           typeof(OnlyWantOne5).Name, o);
   }
}
Note the much simpler syntax - OnlyWantOne5.Instance instead of Singleton5<OnlyWantOne5>.Instance. There are some drawbacks to this approach, primarily that the concrete base class has been used. My experience is that singleton classes don't generally have inheritance relationships, but they could; and this would pose a problem in that case. I have some more interesting uses of generics I hope to post in the future. Let me know if you found this useful!
 
Ramblings, observations, and editorials from my life as a real-world software developer.

ARCHIVES
February 2006 / March 2006 /


Powered by Blogger