使用对象初始值设定项时,为什么编译器会生成额外的局部变量?

2024-01-06

昨天在回答有关 SO 的问题时,我注意到如果使用对象初始化程序初始化对象,编译器会创建一个额外的局部变量。

考虑以下 C# 3.0 代码,在 VS2008 中以发布模式编译:

public class Class1
{
    public string Foo { get; set; }
}

public class Class2
{
    public string Foo { get; set; }
}

public class TestHarness
{
    static void Main(string[] args)
    {
        Class1 class1 = new Class1();
        class1.Foo = "fooBar";

        Class2 class2 =
            new Class2
            {
                Foo = "fooBar2"
            };

        Console.WriteLine(class1.Foo);
        Console.WriteLine(class2.Foo);
    }
}

使用 Reflector,我们可以检查 Main 方法的代码:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] class ClassLibrary1.Class1 class1,
        [1] class ClassLibrary1.Class2 class2,
        [2] class ClassLibrary1.Class2 <>g__initLocal0)
    L_0000: newobj instance void ClassLibrary1.Class1::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldstr "fooBar"
    L_000c: callvirt instance void ClassLibrary1.Class1::set_Foo(string)
    L_0011: newobj instance void ClassLibrary1.Class2::.ctor()
    L_0016: stloc.2 
    L_0017: ldloc.2 
    L_0018: ldstr "fooBar2"
    L_001d: callvirt instance void ClassLibrary1.Class2::set_Foo(string)
    L_0022: ldloc.2 
    L_0023: stloc.1 
    L_0024: ldloc.0 
    L_0025: callvirt instance string ClassLibrary1.Class1::get_Foo()
    L_002a: call void [mscorlib]System.Console::WriteLine(string)
    L_002f: ldloc.1 
    L_0030: callvirt instance string ClassLibrary1.Class2::get_Foo()
    L_0035: call void [mscorlib]System.Console::WriteLine(string)
    L_003a: ret 
}

在这里,我们可以看到编译器生成了两个对实例的引用Class2 (class2 and <>g__initLocal0),但只有一个对实例的引用Class1 (class1).

现在,我对 IL 不太熟悉,但看起来它正在实例化<>g__initLocal0, 设置前class2 = <>g__initLocal0.

为什么会发生这种情况?

那么,使用对象初始值设定项时是否会产生性能开销(即使非常轻微)?


线程安全和原子性。

首先,考虑这行代码:

MyObject foo = new MyObject { Name = "foo", Value = 42 };

任何阅读该声明的人都可能合理地假设foo对象将是原子的。在赋值之前,该对象根本不存在。分配完成后,对象就存在并处于预期状态。

现在考虑翻译该代码的两种可能方法:

// #1
MyObject foo = new MyObject();
foo.Name = "foo";
foo.Value = 42;

// #2
MyObject temp = new MyObject();  // temp will be a compiler-generated name
temp.Name = "foo";
temp.Value = 42;
MyObject foo = temp;

在第一种情况下foo对象在第一行实例化,但直到最后一行执行完毕后才会处于预期状态。如果另一个线程在最后一行执行之前尝试访问该对象,会发生什么情况?该对象将处于半初始化状态。

在第二种情况下foo对象直到最后一行分配时才存在temp。由于引用赋值是一个原子操作,因此它给出的语义与原始的单行赋值语句完全相同。即,foo对象永远不会以半初始化状态存在。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用对象初始值设定项时,为什么编译器会生成额外的局部变量? 的相关文章

随机推荐