【发布时间】:2020-06-13 16:29:22
【问题描述】:
考虑以下 C# 结构定义:
public struct A
{
public B B;
}
public struct B
{
public int C;
}
还要考虑下面的静态方法:
public static int Method(A a) => a.B.C;
调用此方法将生成结构类型A 的副本。例如,在以下代码中:
A a = default;
Method(a);
对Method 的调用将编译为如下所示的IL:
IL_0008: ldloc.0 // V_0
IL_0009: call int32 Class::Method(valuetype A)
ldloc 会将局部变量a (V_0) 的值复制到评估堆栈上,该值将用于Method。如果A(或B)是一个大型结构,那么这个副本可能会很昂贵。 Method 的 IL 也会导致加载值指令:
IL_0000: ldarg.0 // a
IL_0001: ldfld valuetype B A::B
IL_0006: ldfld int32 B::C
IL_000b: ret
最新版本的 C# 包含有助于更高效地使用结构的功能。 C# 7.2 在参数上引入了in 修饰符,当编译器可以验证参数不会被调用的方法修改时,它可以通过引用传递值类型。例如,将in 修饰符应用于参数a:
public static int Method(in A a) => a.B.C;
将在调用站点生成以下编译的 IL:
IL_0008: ldloca.s a
IL_000a: call int32 Class::Method(valuetype A&)
并在执行Method:
IL_0000: ldarg.0 // a
IL_0001: ldflda valuetype B A::B
IL_0006: ldfld int32 B::C
IL_000b: ret
注意加载地址指令。我的假设(如果我错了,请纠正我)是对于深场读取(例如读取 C 内部 B 内部 A ),加载地址指令比加载值指令更有效。
考虑到这一点,考虑更改示例代码:
A a = default;
var c = a.B.C;
然后第二行编译为:
IL_0008: ldloc.1 // V_1
IL_0009: ldfld valuetype B A::B
IL_000e: ldfld int32 B::C
IL_0013: stloc.0 // c
为什么编译器在这种情况下也不喜欢使用加载地址指令呢?是否仅仅因为a 是局部变量而不是方法参数而存在效率差异,还是我在这里遗漏了其他东西?
【问题讨论】:
标签: c# cil intermediate-language