开发者

.NET object creation, whats faster?

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

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

or

MyClass开发者_JS百科 c = new MyClass();
c.Id = 1;
c.Code = "Test";

What's faster? I am assuming there is no difference between the 2.


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.


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.


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


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜