ref readonly 的 C# 行为
Posted
技术标签:
【中文标题】ref readonly 的 C# 行为【英文标题】:C# Behavior of ref readonly 【发布时间】:2019-03-29 06:27:10 【问题描述】:我正在阅读有关 C# 7.2 here 的文档,我在 ref readonly
方面遇到了这个问题:
编译器强制调用者不能修改引用。尝试直接分配值会产生编译时错误。但是,编译器无法知道是否有任何成员方法修改了结构的状态。为确保对象不被修改,编译器创建一个副本并使用该副本调用成员引用。任何修改都是针对该防御性副本。
这给我(可能还有其他一些人)带来了一些困惑,所以我现在想澄清一下这种行为。假设我有一个这样定义的结构:
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
中的值被复制,因此不会更改。我的问题是关于上面提到的“防御性副本”。为什么这是必要的? origin
中的值不能在不调用编译器错误的情况下更改,并且引用会在Point3D.Origin
更新时正确更新,所以有什么理由需要对象的额外副本,这是我通过阅读文档收集的内容,没更新?
【问题讨论】:
您要求我们告诉您这段代码的结果是什么?你不能通过运行代码找到它吗?我们是人,不是计算机。 @mason 我之所以问这个问题,部分原因是我对语言功能感到好奇,还没有升级到 7.2,部分原因是我认为有人会比我更了解它。另外,因为我认为这对将来对此有疑问的人会有所帮助 @mason 是的,我确实可以运行代码。但这并不能告诉我为什么,代码以某种方式运行,现在可以了。这就是提出这个问题的真正动力。而且,如果你知道为什么代码会这样运行,那么你就不需要运行它了。 @mason 简而言之,我对代码的作用不太好奇,而对代码的行为方式为何如此好奇 与 SpinLock 相关的 github 问题。 github.com/dotnet/roslyn/issues/17310 【参考方案1】:您只能在以下情况下为 readonly
字段赋值:
在声明中初始化变量时。
C# 示例:
public readonly int y = 5;
在包含实例字段声明的类的实例构造函数中。
在包含静态字段声明的类的静态构造函数中。
这些构造函数上下文也是唯一可以将readonly
字段作为out
或ref
参数传递的上下文。
如果您使用类似以下示例的语句:
p2.y = 66; // Error
你会得到编译器错误信息:
只读字段不能分配给(除了在构造函数或 变量初始化器)
ref
返回上的 readonly
修饰符表示无法修改返回的引用。以下示例返回对原点的引用。它使用readonly
修饰符来指示调用者不能修改来源:
private static readonly Point origin = new Point(0, 0);
public static ref readonly Point Origin => ref origin;
返回的类型不必是readonly struct
。 ref
可以返回的任何类型都可以ref readonly
返回。
这一切都直接来自文档,我希望它为您澄清!编码愉快!
所以如果你在构造函数中设置(1, 1, 1)
,就会是(1, 1, 1)
用不同的方法调用改变,如上,你得到(0, 0, 0)
【讨论】:
在这种情况下,他们所说的所有这些“防御性复制”业务是什么意思?为什么在请求不可变引用时创建对象的副本? 把副本想象成一个盾牌,如果你试图修改基础,它将修改defensive copy
,基本上使你的修改无效,因为当你使用引用时它会抓住@987654338 @ 那是不变的。您可以继续更改防御副本,但您永远无法通过调用获得它,并且您无法更改 base
顺便说一句,很好地抓住了编译错误。我直接从 Microsoft 文档中提取了我的示例,该文档无法编译 >_
他们可能是在编写 Windows 10 更新的同时编写的。 ;)以上是关于ref readonly 的 C# 行为的主要内容,如果未能解决你的问题,请参考以下文章