.NET对象创建,速度更快?

时间:2010-09-15 06:56:21

标签: c# .net object

这两种对象创建方式之间有区别吗?

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

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

什么更快?我假设2之间没有区别。

4 个答案:

答案 0 :(得分:18)

第二个可能几乎肯定无足轻重更快,因为逻辑上涉及的任务较少。在第一种情况下,代码实际上等同于:

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

当您声明一个 new 变量时,JIT编译器很可能会忽略它们 - 如果您使用对象初始化程序分配给现有变量,它将无法执行此操作

编辑:我刚尝试使用和不使用优化进行编译,在这个“新变量”的情况下,C#编译器忽略了两个 if 它的优化。否则它没有(但JIT仍然可以)。在“重新分配”的情况下,它可以产生可观察到的差异,因此我不希望进行相同的优化。我没有检查过。

我会非常惊讶地看到它实际上产生了显着差异的情况,所以我选择了更易读的选项,IMO是第一个。

编辑:我认为大家可能对基准测试感兴趣,表明它有所作为。这是一个故意可怕的代码,使隐藏的额外任务变慢 - 我已经创建了一个大的,可变的结构。 Urgh。总之...

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;
    }
}

结果(使用/ o + / debug - ):

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

我有点惊讶NewVariableObjectInitializer版本比直接设置版本慢......看起来C#编译器没有像它对引用类型那样优化这种情况。我怀疑在价值类型方面存在一些微妙的因素阻止它。

答案 1 :(得分:4)

我通过创建1亿个对象进行测试,每个对象使用参数化构造函数,带有初始化器的无参数构造函数和带有setter的无参数构造函数,并且根本没有可测量的差异。执行时间略有不同,但是以不同的顺序运行测试会改变结果,因此差异只是由于垃圾收集器在不同时间启动。

创建1亿个对象需要大约1.5秒,因此没有太多理由尝试使其更快。

就个人而言,我更喜欢参数化构造函数,因为我可以将属性设置器设置为私有,这样如果我愿意,我可以使该类不可变:

class MyClass {

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

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

}

此外,通过这种方式,您可以确保在创建对象时正确设置了所有属性。

答案 2 :(得分:3)

为了说明M Skeet的代码,这里是IL(注意方法#1的附加ldloc stloc)

  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

答案 3 :(得分:2)

他们是一样的。但我们都更喜欢第一个,它更可读,更清晰,不是吗?