我正在尝试将a传递readonly struct给带有in修饰符的方法。当我查看生成的IL代码时,似乎已经构造了只读结构的防御性副本。
在readonly struct被定义为
public readonly struct ReadonlyPoint3D
{
public ReadonlyPoint3D(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public double X { get; }
public double Y { get; }
public double Z { get; }
}
Run Code Online (Sandbox Code Playgroud)
接受的方法 ReadonlyPoint3D
private static double CalculateDistance(in ReadonlyPoint3D point1, in ReadonlyPoint3D point2)
{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;
return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}
Run Code Online (Sandbox Code Playgroud)
以及我调用此方法的方式:
static void Main(string[] args)
{
var point1 = new ReadonlyPoint3D(0, 0, 0);
var point2 = new ReadonlyPoint3D(1, 1, 1);
var distance = CalculateDistance(in point1, in point2);
}
Run Code Online (Sandbox Code Playgroud)
如果查看为CalculateDistance方法调用生成的IL ,我会看到ReadonlyPoint3D实例是通过引用传递的:
IL_0045: ldloca.s point1
IL_0047: ldloca.s point2
IL_0049: call float64 CSharpTests.Program::CalculateDistance(valuetype CSharpTests.ReadonlyPoint3D&, valuetype CSharpTests.ReadonlyPoint3D&)
IL_004e: stloc.2 // distance
Run Code Online (Sandbox Code Playgroud)
但是,CalculateDistance方法的IL似乎会复制point1&point2参数:
// [25 9 - 25 10]
IL_0000: nop
// [26 13 - 26 54]
IL_0001: ldarg.0 // point1
IL_0002: call instance float64 CSharpTests.ReadonlyPoint3D::get_X()
IL_0007: ldarg.1 // point2
IL_0008: call instance float64 CSharpTests.ReadonlyPoint3D::get_X()
IL_000d: sub
IL_000e: stloc.0 // xDifference
// the resit is omitted for the sake of brevity, essentially same code repeated for Y & Z
Run Code Online (Sandbox Code Playgroud)
ldarg.0&ldarg.1in CalculateDistance方法生成的IL使我认为是point1&的副本point2。我期望在这里看到的是ldloca.s我认为要加载point1&地址的指令point2。
我是否正确理解并制作了防御性副本?还是我对IL代码的解释是错误的?
我在C#7.3中使用.NET Core 2.1
编辑
根据Microsoft文档,使用in修饰符传递的可变结构将创建防御性副本。
如果我定义了可变结构
public struct MutablePoint3D
{
public MutablePoint3D(double x, double y, double z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
并通过 in
private static double CalculateDistance(in MutablePoint3D point1, in MutablePoint3D point2)
{
double xDifference = point1.X - point2.X;
double yDifference = point1.Y - point2.Y;
double zDifference = point1.Z - point2.Z;
return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}
Run Code Online (Sandbox Code Playgroud)
我可以看到生成的IL代码类似于生成的代码readonly struct:
// [26 13 - 26 54]
IL_0001: ldarg.0 // point1
IL_0002: call instance float64 CSharpTests.MutablePoint3D::get_X()
IL_0007: ldarg.1 // point2
IL_0008: call instance float64 CSharpTests.MutablePoint3D::get_X()
IL_000d: sub
IL_000e: stloc.0 // xDifference
// the resit is omitted for the sake of brevity
Run Code Online (Sandbox Code Playgroud)
另一个观察结果是,如果我in从CalculateDisctance接受方法中删除修饰符ReadonlyPoint3D,则生成的IL代码正是我所期望的
// [35 13 - 35 54]
IL_0001: ldarga.s point1
IL_0003: call instance float64 CSharpTests.ReadonlyPoint3D::get_X()
IL_0008: ldarga.s point2
IL_000a: call instance float64 CSharpTests.ReadonlyPoint3D::get_X()
IL_000f: sub
IL_0010: stloc.0 // xDifference
Run Code Online (Sandbox Code Playgroud)
但这似乎与Microsoft Docs中的建议不符
编辑2
正如@PetSerAl在评论中所建议的,sharplab.io为该代码生成了不同的IL。差异- ldobj仅针对的指令CalculateDistance(in MutablePoint3D point1, in MutablePoint3D point2)将说明仅针对这种情况进行了防御性复制。
但是,问题中张贴的IL指令取自ReSharper的IL Viewer,并通过ILDASM.exe工具进行了验证(用于Release配置,如sharplab.io)。因此,我不确定这种差异来自何处以及哪个输出值得信任。