tags:

views:

173

answers:

4

IS there a difference between those 2 ways of object creation?

new MyClass() { Id = 1, Code = "Test" };

or

MyClass c = new MyClass();
c.Id = 1;
c.Code = "Test";

Whats faster? I am assuming there is no difference between the 2.

Thanks :-)

+2  A: 

They are the same. But we all prefer the first one, it's more readable and clearer, isn't it?

Danny Chen
+12  A: 

The second will possibly be almost certainly insignificantly faster, because there's one fewer assignment involved, logically. In the first case, the code is actually equivalent to:

MyClass tmp = new MyClass()
tmp.Id = 1;
tmp.Code = "Test";
MyClass c = tmp;

It's very possible that the JIT compiler will elide these as you're declaring a new variable - it wouldn't be able to do so if you were assigning to an existing variable with an object initializer.

EDIT: I've just tried compiling with and without optimizations turned on, and in this "new variable" case the C# compiler elides the two if it's optimizing. It doesn't otherwise (but the JIT still could). In the "reassignment" case it could make an observable difference, so I wouldn't expect the same optimization. I haven't checked though.

I would be very surprised to see a situation where it actually made a significant difference though, so I'd go with the more readable option, which IMO is the first.

EDIT: I thought folks might be interested in a benchmark showing it making a difference. This is deliberately horrible code to make the hidden extra assignment slow - I've created a big, mutable struct. Urgh. Anyway...

using System;
using System.Diagnostics;

struct BigStruct
{
    public int value;
    #pragma warning disable 0169
    decimal a1, a2, a3, a4, a5, a6, a7, a8;
    decimal b1, b2, b3, b4, b5, b6, b7, b8;
    decimal c1, c2, c3, c4, c5, c6, c7, c8;
    decimal d1, d2, d3, d4, d5, d6, d7, d8;
    #pragma warning restore 0169
}

class Test
{
    const int Iterations = 10000000;

    static void Main()
    {
        Time(NewVariableObjectInitializer);
        Time(ExistingVariableObjectInitializer);
        Time(NewVariableDirectSetting);
        Time(ExistingVariableDirectSetting);
    }

    static void Time(Func<int> action)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        Console.WriteLine("{0}: {1}ms",
                          action.Method.Name,
                          stopwatch.ElapsedMilliseconds);
    }

    static int NewVariableObjectInitializer()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableObjectInitializer()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int NewVariableDirectSetting()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableDirectSetting()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }
}

Results (with /o+ /debug-):

NewVariableObjectInitializer: 3328ms
ExistingVariableObjectInitializer: 3300ms
NewVariableDirectSetting: 1464ms
ExistingVariableDirectSetting: 1491ms

I'm somewhat surprised that the NewVariableObjectInitializer version is slower than the direct setting ones... it looks like the C# compiler doesn't optimize this case in the way that it does for reference types. I suspect there's some subtlety around value types that prevents it.

Jon Skeet
Have overload constructors been killed by the object initializer?
Michael Shimmins
Oh I don't know it will create a tmp instance! But why?
Danny Chen
@Michael: Not if you want your type to be immutable.
Jon Skeet
@Danny: It doesn't create a temporary *instance* - it creates a temporary *variable*. That's important if you're reassigning the value of an existing variable, as if you read the code the assignment *looks* as if it's performed after creating the new object *and setting the properties*. The temporary variable achieves those semantics - you never end up with the variable in a state between the old value and the new-but-uninitialized value.
Jon Skeet
+2  A: 

To illustrate M Skeet's code, here's the IL (note the additional ldloc stloc for method #1)

  IL_0001:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0006:  stloc.2
  IL_0007:  ldloc.2
  IL_0008:  ldc.i4.1
  IL_0009:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_000e:  nop
  IL_000f:  ldloc.2
  IL_0010:  ldstr      "Test"
  IL_0015:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_001a:  nop
  IL_001b:  ldloc.2
  IL_001c:  stloc.0


  IL_001d:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0022:  stloc.1
  IL_0023:  ldloc.1
  IL_0024:  ldc.i4.1
  IL_0025:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_002a:  nop
  IL_002b:  ldloc.1
  IL_002c:  ldstr      "Test"
  IL_0031:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_0036:  nop
vc 74
There are lots of `nop` operations in it. Is this a debug build?
nikie
Good point, the release code does not contain the additional ld st
vc 74
+3  A: 

I tested by creating 100 million objects each using a parameterised constructor, a parameterless constructor with initialiser, and a parameterless constructor with setters, and there is no measurable difference at all. There was a slight difference in execution time, but running the tests in different order changed the results, so the differences are just due to the garbage collector kicking in at different times.

Creating 100 million objects took about 1.5 seconds, so there isn't much reason to try to make it faster either.

Personally I prefer a parameterised constructor, as I then can make the property setters private so that I can make the class immutable if I want to:

class MyClass {

  public int Id { get; private set; }
  public string Code { get; private set; }

  public MyClass(int id, string code) {
    Id = id;
    Code = code;
  }

}

Also, this way you can make sure that all properties have been set properly when the object is created.

Guffa