Geeks With Blogs
.NET Nomad What I've learned along the way

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.

Posted on Saturday, January 3, 2009 2:20 PM | Back to top


Comments on this post: Surprising things your colleagues may not know (C# .NET Generic Constraints)

# re: Surprising things your colleagues may not know (C# .NET Generic Constraints)
Requesting Gravatar...
@Tuna Toksoz

Yeah, that is another way that the IMHO "bad" code could have been written. It still would only throw an error at runtime.

Using the new() will force an error at compile time.

The ObjectFactory example is a total mock up and doesn't represent the real usage scenario. It only illustrates the bad code.
Left by newman on Jan 05, 2009 2:42 PM

# re: Surprising things your colleagues may not know (C# .NET Generic Constraints)
Requesting Gravatar...
I think the original author of the CreateObject() method overlooked the types without default constructors and the possibility of the NullReferenceException you have mentioned.
Left by Jawwad Alam on Jan 06, 2009 1:37 PM

# re: Surprising things your colleagues may not know (C# .NET Generic Constraints)
Requesting Gravatar...
Good point, thanks for the memo.
Left by Durukan Duru on Jan 29, 2010 8:11 AM

Your comment:
 (will show your gravatar)


Copyright © newman | Powered by: GeeksWithBlogs.net