The other day I was digging through some source code and stumbled upon something similar to this:
public class ObjectFactory<T> where T : new()
{
public T Construct()
{
return new T();
}
}
I was curious what the generic constraint new() meant, but didn't have time to investigate so just scribbled on the whiteboard in my office to investigate it over the weekend.
Fast forward to this weekend. Saturday ( today ) was an unbelievable beautiful day in Sarasota, Florida and the kids and I decided to spend most of the day playing in the backyard. During my breaks I was reading How to Code .NET: Tips and Tricks for Coding .NET 1.1 and .NET 2.0 Applications Effectively.
There is a chapter in this book, called Using the new and class keywords with .NET Generics, which preceded to explain that the new() constraint meant the type must have a parameterless constructor. Sweet! Cross that question off the list.
The book then started to dive into IL and talk about the performance implications of including the class constraint as follows:
public class ObjectFactory<T> where T : class, new()
{
public T Construct()
{
return new T();
}
}
Although sadly I hadn't thought about it, without the class constraint, the compiler doesn't know if the type T is a value type or reference type and hence has to check for both.
So I am hanging out having fun with my kids, but there is a nagging desire in the back of my head to run ildasm and look at the IL for myself. And, yeah, there is a difference. Here is the IL when you don't include the class constraint:
.method public hidebysig instance !T Construct() cil managed
{
// Code size 38 (0x26)
.maxstack 2
.locals init ([0] !T CS$1$0000,
[1] !T CS$0$0001)
IL_0000: nop
IL_0001: ldloca.s CS$0$0001
IL_0003: initobj !T
IL_0009: ldloc.1
IL_000a: box !T
IL_000f: brfalse.s IL_001c
IL_0011: ldloca.s CS$0$0001
IL_0013: initobj !T
IL_0019: ldloc.1
IL_001a: br.s IL_0021
IL_001c: call !!0 [mscorlib]System.Activator::CreateInstance<!T>()
IL_0021: stloc.0
IL_0022: br.s IL_0024
IL_0024: ldloc.0
IL_0025: ret
} // end of method ObjectFactory`1::Construct
and here is the code when you do include the class constraint:
.method public hidebysig instance !T Construct() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] !T CS$1$0000)
IL_0000: nop
IL_0001: call !!0 [mscorlib]System.Activator::CreateInstance<!T>()
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method ObjectFactory`1::Construct
When you include the class constraint, we jump immediately into Activator.CreateInstance because we know the type T is not a value type. I won't lose sleep at night if I forget the class constraint when I only mean reference types, but certainly I am performance conscious and will include the class constraint in code where I am definitely assuming T is a reference type.
Also, if you have never played with SqlCommandBuilder.DeriveParameters, I also played with it this weekend:
by David Hayden
Posted
Sat, Nov 4 2006 5:29 PM
by
David Hayden