14

If I have a type parameter constraint new():

void Foo<T>() where T : new()
{
    var t = new T();
}

Is it true that new T() will internally use the Activator.CreateInstance method (i.e. reflection)?

stakx - no longer contributing
  • 83,039
  • 20
  • 168
  • 268
msfanboy
  • 5,273
  • 13
  • 69
  • 120
  • 6
    @Oded If you are trying to better understand the environment that you are programming in, then yes - it does matter! :-) – Justin Jul 15 '11 at 14:26
  • 1
    @Kragen: ildasm, monodis – sehe Jul 15 '11 at 14:28
  • 1
    @sehe IL Spy is my decompiler of choice at the moment - the times when I've jumped in and decompiled Microsoft assemblies (to try and answer questions like this) have actually been very enlightening and have had a pretty big impact on my coding style. – Justin Jul 15 '11 at 15:05
  • Note that the behaviour has changed with Roslyn. Preiously `Activator.CreateInstance` wasnt called if `struct` constraint was added. Today regardless of constraint IL will have call to `Activator.CreateInstance`. Very similar q: http://stackoverflow.com/questions/367577/why-does-the-c-sharp-compiler-emit-activator-createinstance-when-calling-new-in – nawfal Jul 01 '16 at 01:09

2 Answers2

11

Yes, this is true. Edit 2: Here's a good explanation of the how and why.

http://www.simple-talk.com/community/blogs/simonc/archive/2010/11/17/95700.aspx

For verification I compiled the following method:

public static T Create<T>() where T: new() {
    return new T();
}

And this is the generated IL when compiled with the C# compiler in .NET 3.5 SP1:

.method public hidebysig static !!T Create<.ctor T>() cil managed
{
    .maxstack 2
    .locals init (
        [0] !!T local,
        [1] !!T local2)
    L_0000: ldloca.s local
    L_0002: initobj !!T
    L_0008: ldloc.0 
    L_0009: box !!T
    L_000e: brfalse.s L_001a
    L_0010: ldloca.s local2
    L_0012: initobj !!T
    L_0018: ldloc.1 
    L_0019: ret 
    L_001a: call !!0 [mscorlib]System.Activator::CreateInstance<!!T>()
    L_001f: ret 
}

Edit: The C# 4 compiler creates slightly different, but similar, code:

.method public hidebysig static !!T Create<.ctor T>() cil managed
{
    .maxstack 2
    .locals init (
        [0] !!T CS$1$0000,
        [1] !!T CS$0$0001)
    L_0000: nop 
    L_0001: ldloca.s CS$0$0001
    L_0003: initobj !!T
    L_0009: ldloc.1 
    L_000a: box !!T
    L_000f: brfalse.s L_001c
    L_0011: ldloca.s CS$0$0001
    L_0013: initobj !!T
    L_0019: ldloc.1 
    L_001a: br.s L_0021
    L_001c: call !!0 [mscorlib]System.Activator::CreateInstance<!!T>()
    L_0021: stloc.0 
    L_0022: br.s L_0024
    L_0024: ldloc.0 
    L_0025: ret 
}

In the case of a value type it doesn't use the activator but just returns the default(T) value, otherwise it invokes the Activator.CreateInstance method.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • @Jason: `initobj` is defined as "Initializes each field of the value type at a specified address to a null reference or a 0 of the appropriate primitive type. ... Unlike Newobj, initobj does not call the constructor method. Initobj is intended for initializing value types, while newobj is used to allocate and initialize objects." There is no indication that it calls the default constructor of a reference type. I assume it's equivalent to `default(T)` – CodesInChaos Jul 15 '11 at 14:53
  • @CodeInChaos: I already deleted my comment. I made a mistake. – jason Jul 15 '11 at 14:54
  • 1
    Okay, so the big question is why. Why isn't the compiler just emitting a `newobj` instruction? – jason Jul 15 '11 at 14:57
  • So what happens if the constraint is `class, new()` ? – Michael Petito Jul 15 '11 at 15:05
  • It still uses the activator - I assume because the expected token for `newobj` is a constructor token, not a type token. – Lucero Jul 15 '11 at 15:08
  • @Jason: because the `newobj` instruction requires a methodref token for a specific ctor as an argument. If you call a method on a generic type with a type constraint, the compiler can issue a `callvirt` instruction using a methrodref token to the method on the type specified by the constraint. But the `new()` constraint doesn't specify a type, so the compiler doesn't know what constructor will be called until `T` is actually bound to a type. It therefore has no way to know what methodref token to pass to `newobj`, hence it cannot use it. – Sven Jul 15 '11 at 15:11
  • TL;DR --> **[Code Sharing](http://www.google.nl/search?client=opera&rls=en&q=code+sharing+generics&sourceid=opera&ie=utf-8&oe=utf-8&channel=suggest)**, see also [Miguel DeIcaza](http://tirania.org/blog/archive/2008/Apr-16.html) – sehe Jul 15 '11 at 15:38
  • @Lucero The reason why I asked this question because I wanted to use T for mapping business object properties to a datarow/datatable columns. But when they both use Reflection to create the Closed Type well... – msfanboy Jul 15 '11 at 17:26
  • @msfanboy, you can use `Relfection.Emit` (and `DynamicMethod`s) to generically create objects more efficiently. – Lucero Jul 18 '11 at 20:54
1

Yes. It does for reference types.

Using ILSpy on the following release-compiled code:

    public static void DoWork<T>() where T: new()
    {
        T t = new T();
        Console.WriteLine(t.ToString());
    }

Yielded

.method public hidebysig 
    instance void DoWork<.ctor T> () cil managed 
{
    // Method begins at RVA 0x2064
    // Code size 52 (0x34)
    .maxstack 2
    .locals init (
        [0] !!T t,
        [1] !!T CS$0$0000,
        [2] !!T CS$0$0001
    )

    IL_0000: ldloca.s CS$0$0000
    IL_0002: initobj !!T
    IL_0008: ldloc.1
    IL_0009: box !!T
    IL_000e: brfalse.s IL_001b

    IL_0010: ldloca.s CS$0$0001
    IL_0012: initobj !!T
    IL_0018: ldloc.2
    IL_0019: br.s IL_0020

    IL_001b: call !!0 [mscorlib]System.Activator::CreateInstance<!!T>()

    IL_0020: stloc.0
    IL_0021: ldloca.s t
    IL_0023: constrained. !!T
    IL_0029: callvirt instance string [mscorlib]System.Object::ToString()
    IL_002e: call void [mscorlib]System.Console::WriteLine(string)
    IL_0033: ret
} // end of method Program::DoWork

Or in C#:

public void DoWork<T>() where T : new()
{
    T t = (default(T) == null) ? Activator.CreateInstance<T>() : default(T);
    Console.WriteLine(t.ToString());
}

JIT will create different compiled instructions for each different value type parameter passed in, but will use the same instructions for reference types -- hence the Activator.CreateInstance()

foson
  • 10,037
  • 2
  • 35
  • 53