ref readonly 的 C# 行为

C# Behavior of ref readonly

我正在阅读有关 C# 7.2 的文档 here,我在 ref readonly 方面遇到了这个问题:

The compiler enforces that the caller can't modify the reference. Attempts to assign the value directly generate a compile-time error. However, the compiler can't know if any member method modifies the state of the struct. To ensure that the object isn't modified, the compiler creates a copy and calls member references using that copy. Any modifications are to that defensive copy.

这让我(可能还有其他人)有些困惑,所以我现在想澄清一下这个行为。假设我有一个这样定义的结构:

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    public static ref readonly Point3D Origin => ref origin;

    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }

    public static void ChangeOrigin(int x = 0, int y = 0, int z = 0)
    {
        origin = new Point3D(x, y, z);
    }
}

现在,假设我使用 ref readonly 得到 Point3D.Origin 并修改了它:

ref readonly var origin = ref Point3D.Origin;
var originValue = Point3D.Origin;
Point3D.ChangeOrigin(1, 1, 1);

Console.WriteLine("Origin is: ({0}, {1}, {2})", origin.X, origin.Y, origin.Z);
Console.WriteLine("Origin is: ({0}, {1}, {2})", originValue.X, originValue.Y, originValue.Z);

运行 这段代码的结果是:

Origin is: (1, 1, 1)
Origin is: (0, 0, 0)

这是意料之中的。 origin 中的值在我调用 ChangeOrigin 时更新,而 originValue 中的值被复制,因此不会改变。我的问题是关于上面提到的 "defensive copy"。为什么这是必要的? origin 中的值不能在不调用编译器错误的情况下更改,并且当 Point3D.Origin 更新时引用会正确更新,所以有什么理由要有一个额外的对象副本,这是我收集的阅读文档,没有更新?

您只能在以下情况下为 readonly 字段赋值:

  • 在声明中初始化变量时

    C# 示例:

     public readonly int y = 5;
    
  • 在包含实例字段声明的 class 的实例构造函数中。

  • 在包含静态字段声明的class的静态构造函数中。

这些构造函数上下文也是唯一可以将 readonly 字段作为 outref 参数有效传递的上下文。

如果您使用如下示例的语句:

p2.y = 66; // Error

您将收到编译器错误消息:

A readonly field cannot be assigned to (except in a constructor or a variable initializer)

ref return 上的 readonly 修饰符表示无法修改 returned 引用。以下示例 return 是对原点的引用。它使用 readonly 修饰符来指示调用者不能修改来源:

private static readonly Point origin = new Point(0, 0);
public static ref readonly Point Origin => ref origin;

类型 returned 不需要是 readonly struct。 return 可以被 ref 编辑的任何类型都可以被 ref readonly 编辑 return。

这些都是直接来自文档,我希望它能为您澄清!编码愉快!

因此,如果您在构造函数中设置 (1, 1, 1),它将 (1, 1, 1) 使用不同的方法调用更改,如上所述,您会得到 (0, 0, 0)