.NET Nomad

What I've learned along the way

  Home  |   Contact  |   Syndication    |   Login
  13 Posts | 0 Stories | 50 Comments | 0 Trackbacks

News

Archives

Post Categories

Saturday, January 03, 2009 #

If you are like me, you may be surprised to find that your colleagues don't know some fundamental things about Generics in .NET. 

I just began working on a new project at work and during the design phase I was assured that our company had a great API full of extensions for a commercial content management solution.  This API, I was told, would revolutionize how I used the product and take a lot of burden off my team of developers (some of which had used the API on previous projects and thought it was spot on).  Being the trusting lad that I am, I agreed to this site unseen (lesson learned I assure you).  The following snippet of code exemplifies the use of .NET Generics throughout the API and represents the first instance of code smell that would spiral into sleepless nights of refactoring the API like a doctor fighting gangrene:

public class ObjectFactory<T>
    {

        public static T CreateObject()
        {

            T obj = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);

            return obj;

        }

    }

As you can see from the above we not only have an example of a misunderstanding of Generics, but also one that displays very unsafe practices in reflection.  For those of you that don't wish to interpret the code above the line in question:

T obj = (T)typeof(T).GetConstructor(new Type[0]).Invoke(new object[0]);

is simply using reflection to get a reference to T's default constructor and then invoking it in order to instantiate an object of type T.  This is all well and good, but what happens if T doesn't have a default constructor?  In the snippet shown above such a situation would result in a NullReferenceException being thrown at runtime.  In this case a NullReferenceException, although accurate, isn't semantically correct since it doesn't tell you what the true error is (i.e. T does not have a default constructor).  The following code still uses reflection, but is rewritten to be a bit more semantically correct:

public static T CreateObject() {

   ConstructorInfo cons = typeof(T).GetConstructor(Type.EmptyTypes);
   T obj = default(T);

   if(cons == null)
       throw new NoDefaultConstructorException();

   try
   {

        obj = (T)cons.Invoke(null);

   }
   catch (Exception ex)
   {

        throw new Exception("Exception encountered while invoking default constructor", ex);

   }

   return obj;

}

Awesome. We now do some basic error checking and throw a more appropriate exception.  This is still not ideal because our application can only detect the larger error, the fact that someone is using a T that has no default constructor, at runtime.  It would be much better if we push the burden of checking this fact to the compiler.  In order to do this we make use of .NET Generic Constraints as so:

 

public class ObjectFactory<T> where T : new()
    {

        public static T CreateObject()
        {

            return new T();

        }
    }

}
 

If we write a simple program to use our ObjectFactory, we can see that specifying a class that does not provide a default constructor will now generate a compiler error:

class Program
    {
        static void Main(string[] args)
        {

            HasDefaultConstructor obj = ObjectFactory<HasDefaultConstructor>.CreateObject();
            NoDefaultConstructor noCons = ObjectFactory<NoDefaultConstructor>.CreateObject();

            Console.WriteLine("Done");
            Console.ReadKey();

        }
    }

Much better! 

There is a relatively small number of Constraints that can be applied and it is how one chooses to combine them that makes them powerful.  The full list of constraints can be found on MSDN.