为什么值类型字段可能被实例化,而不是被初始化?
Why value type fields may be instantiated, instead of initialized?
如果 S
声明了构造函数,则通过调用其构造函数(例如 a = new S(11)
)初始化值类型局部变量(例如 S s;
给定 struct S{}
)使用 int
参数。然后 new S(11)
编译为:
ldloca.s V_0
ldc.i4.s 11
call instance void S::.ctor(int32)
然而,当 s
是一个字段时(例如 class C { S s;
),则它不会以相同的方式初始化。关于 class C
的以下实例方法: void setS(int n) { s = new S(n); }
它将编译为:
ldarg.0
ldarg.1
newobj instance void S::.ctor(int32)
stfld valuetype S C::s
我原以为它会编译成下面的代码,这更接近局部变量的情况:
ldarg.0
ldflda valuetype S C::s
ldarg.1
call instance void S::.ctor(int32)
另外,如果我理解清楚的话,newobj instance void S::.ctor(int32)
的开销和GC负担都比较大。我说得对吗?
为什么 C# 编译器对值类型字段使用与局部变量不同的方法?
基本上,为了将构造函数调用与赋值分开,需要此行为。
预期的可观察行为是,如果构造函数抛出异常,则不会进行赋值。在构造函数直接写入 field/stack 槽的 "optimized" 版本中不会出现这种情况。
如果满足以下条件,您可以在分配给局部变量时看到相同的 IL:
- 该变量之前已经声明并赋值
- 如果抛出异常,变量仍然可以访问
不完全相同的问题,但有更多细节in this SO post and on Eric Lippert's blog。
如果 S
声明了构造函数,则通过调用其构造函数(例如 a = new S(11)
)初始化值类型局部变量(例如 S s;
给定 struct S{}
)使用 int
参数。然后 new S(11)
编译为:
ldloca.s V_0
ldc.i4.s 11
call instance void S::.ctor(int32)
然而,当 s
是一个字段时(例如 class C { S s;
),则它不会以相同的方式初始化。关于 class C
的以下实例方法: void setS(int n) { s = new S(n); }
它将编译为:
ldarg.0
ldarg.1
newobj instance void S::.ctor(int32)
stfld valuetype S C::s
我原以为它会编译成下面的代码,这更接近局部变量的情况:
ldarg.0
ldflda valuetype S C::s
ldarg.1
call instance void S::.ctor(int32)
另外,如果我理解清楚的话,newobj instance void S::.ctor(int32)
的开销和GC负担都比较大。我说得对吗?
为什么 C# 编译器对值类型字段使用与局部变量不同的方法?
基本上,为了将构造函数调用与赋值分开,需要此行为。
预期的可观察行为是,如果构造函数抛出异常,则不会进行赋值。在构造函数直接写入 field/stack 槽的 "optimized" 版本中不会出现这种情况。
如果满足以下条件,您可以在分配给局部变量时看到相同的 IL:
- 该变量之前已经声明并赋值
- 如果抛出异常,变量仍然可以访问
不完全相同的问题,但有更多细节in this SO post and on Eric Lippert's blog。