我经常听说使用值类型时我们必须小心将它们传递给方法,因为值将被复制。如果结构很大,则可能是一个问题。
但是我看了IL的代码:
using System;
public class Program
{
public struct Big
{
public int a, b, c, d;
}
public static void Main()
{
Big big = new Big();
UseBig(big);
big.a += 7;
}
private static void UseBig(Big big)
{
big.a += 1;
big.b += 1;
}
}
IL:
.method public hidebysig static void Main() cil managed
{
//
.maxstack 3
.locals init (valuetype Program/Big V_0)
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: initobj Program/Big
IL_0009: ldloc.0
IL_000a: call void Program::UseBig(valuetype Program/Big)
IL_000f: nop
IL_0010: ldloca.s V_0
IL_0012: dup
IL_0013: ldfld int32 Program/Big::a
IL_0018: ldc.i4.7
IL_0019: add
IL_001a: stfld int32 Program/Big::a
IL_001f: ret
} // end of method Program::Main
.method private hidebysig static void UseBig(valuetype Program/Big big) cil managed
{
//
.maxstack 8
IL_0000: nop
IL_0001: ldarga.s big
IL_0003: dup
IL_0004: ldfld int32 Program/Big::a
IL_0009: ldc.i4.1
IL_000a: add
IL_000b: stfld int32 Program/Big::a
IL_0010: ldarga.s big
IL_0012: dup
IL_0013: ldfld int32 Program/Big::b
IL_0018: ldc.i4.1
IL_0019: add
IL_001a: stfld int32 Program/Big::b
IL_001f: ret
} // end of method Program::UseBig
调用函数的机制是它load location of local variable #0
到堆栈,然后在它使用的函数load address of argument
内。然后我们可以使用此地址load field
和store field
。 dup
因为load
和store
会使用堆栈顶部的地址。
使用ref
进行另一次运行:
using System;
public class Program
{
public struct Big
{
public int a, b, c, d;
}
public static void Main()
{
Big big = new Big();
UseBig(ref big);
big.a += 7;
}
private static void UseBig(ref Big big)
{
big.a += 1;
big.b += 1;
}
}
IL:
.method public hidebysig static void Main() cil managed
{
//
.maxstack 3
.locals init (valuetype Program/Big V_0)
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: initobj Program/Big
IL_0009: ldloca.s V_0
IL_000b: call void Program::UseBig(valuetype Program/Big&)
IL_0010: nop
IL_0011: ldloca.s V_0
IL_0013: dup
IL_0014: ldfld int32 Program/Big::a
IL_0019: ldc.i4.7
IL_001a: add
IL_001b: stfld int32 Program/Big::a
IL_0020: ret
} // end of method Program::Main
.method private hidebysig static void UseBig(valuetype Program/Big& big) cil managed
{
//
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: dup
IL_0003: ldfld int32 Program/Big::a
IL_0008: ldc.i4.1
IL_0009: add
IL_000a: stfld int32 Program/Big::a
IL_000f: ldarg.0
IL_0010: dup
IL_0011: ldfld int32 Program/Big::b
IL_0016: ldc.i4.1
IL_0017: add
IL_0018: stfld int32 Program/Big::b
IL_001d: ret
} // end of method Program::UseBig
在输入函数时,它改为使用load local variable address
,因此地址在函数内部而不是它的值,而不是直接加载它的地址我们load argument
值,因为它已经是一个地址。我们在这里所做的一切都将影响调用者的值,因为它是对调用者的地址而不是参数变量空间。因此,这是ref
的行为。
问题是:
struct
(c
和d
)中的任何其他变量?无论它有多大,程序实际上都将整个结构作为单个地址传递。那么人们一直在谈论的成本在哪里呢?ref
实际上没有保存任何“结构复制成本”?这些IL代码是从https://dotnetfiddle.net/
获得的答案 0 :(得分:2)
在第一个版本中,副本由
生成ldloc.0
没有传递给该函数的地址。
无论如何,你不能依赖IL检查,尝试查看生成的本机代码。即使在第一种情况下,内联可能会发生并优化掉该副本。