这是传递给带有 in 关键字的方法的只读结构的防御副本吗

Is this a defensive copy of readonly struct passed to a method with in keyword

我正在尝试将 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; }
}

接受的方法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);
}

以及我调用此方法的方式:

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);
}

如果我查看为 CalculateDistance 方法调用生成的 IL,我发现 ReadonlyPoint3D 个实例是 passed by reference:

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

然而,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
CalculateDistance 方法生成的 IL 中的

ldarg.0 & ldarg.1 让我认为 point1 & point2 的副本已经制作完成。 我期待在这里看到的是 ldloca.s 指令,我认为这意味着加载 point1 & point2.

的地址

我的理解正确吗,制作防御副本? 或者我对 IL 代码的解释是错误的?

我正在使用 .NET Core 2.1 和 C# 7.3


编辑

根据 Microsoft 文档,使用 in 修饰符传递的可变结构将具有 defensive copies created.

如果我定义可变结构

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; }
}

并用 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);
}

我可以看到生成的 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

另一个观察结果是,如果我从接受 ReadonlyPoint3DCalculateDisctance 方法中删除 in 修饰符,生成的 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

但这似乎并不对应the suggestion in Microsoft Docs


编辑 2

正如@PetSerAl 在评论中所建议的那样,sharplab.io produces different IL 此代码。 差异 - ldobj 仅针对 CalculateDistance(in MutablePoint3D point1, in MutablePoint3D point2) 的指令将解释防御性复制仅针对这种情况进行。

但是,问题中发布的 IL 指令取自 ReSharper 的 IL Viewer,并由 ILDASM.exe 工具验证(对于发布配置,如 sharplab.io 中)。所以我不确定这种差异从何而来以及哪个输出值得信任。

可以在 related GitHub issue 上找到长篇讨论。

本质上这是一个 Roslyn bug 已修复,最新版本的 VS 2019(16.2 及更高版本)已修复。