这是声明不可变结构的正确方法吗?
public struct Pair
{
public readonly int x;
public readonly int y;
// Constructor and stuff
}
我无法想到为什么这会遇到问题,但我只是想要确认。
在这个例子中,我使用了int。如果我使用了一个类,但该类也是不可变的,就像这样呢?这应该也可以,对吗?
public struct Pair
{
public readonly (immutableClass) x;
public readonly (immutableClass) y;
// Constructor and stuff
}
(旁白:我明白使用属性更具概括性并且允许更改,但是这个结构只是为了存储两个值。我只是对这里的不变性问题感兴趣。)
答案 0 :(得分:111)
如果您要使用结构体,最佳做法是使它们不可变。
使所有字段只读是一种很好的方法来帮助(1)记录结构是不可变的,以及(2)防止意外突变。
然而,有一个皱纹,这实际上是一个奇怪的巧合,我计划在下周写博客。那就是: readonly在struct字段上是谎言。人们期望只读字段不能改变,但当然可以。结构字段上的“readonly”是在帐户中没有钱的情况下编写支票的声明。 结构体不拥有其存储空间,而且存储体可以变异。
例如,让我们拿你的结构:
public struct Pair
{
public readonly int x;
public readonly int y;
public Pair(int x, int y)
{
this.x = x;
this.y = y;
}
public void M(ref Pair p)
{
int oldX = x;
int oldY = y;
// Something happens here
Debug.Assert(x == oldX);
Debug.Assert(y == oldY);
}
}
是否有任何事情可能发生在“此处发生的事情”导致调试断言被违反?肯定。
public void M(ref Pair p)
{
int oldX = this.x;
int oldY = this.y;
p = new Pair(0, 0);
Debug.Assert(this.x == oldX);
Debug.Assert(this.y == oldY);
}
...
Pair myPair = new Pair(10, 20);
myPair.M(ref myPair);
现在发生了什么?断言被违反了! “this”和“p”指的是相同的存储位置。存储位置发生了变异,因此“this”的内容发生了变异,因为它们是相同的。结构不能强制执行x和y的只读,因为结构不拥有存储;存储是一个局部变量,可以根据需要随意变异。
你不能依赖在结构中的readonly字段永远不会被观察到变化的不变量;您唯一可以依赖的是您无法编写直接更改它的代码。但是,通过像这样的一点偷偷摸摸的工作,你可以间接改变它你想要的一切。
另见Joe Duffy关于此问题的优秀博客文章:
http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/
答案 1 :(得分:5)
这确实会让它变得一成不变。我想你最好添加一个构造函数 如果它的所有成员也是不可变的,那么这将使它完全不可变。这些可以是类或简单值。
答案 2 :(得分:4)
从C#7.2开始,您现在可以将整个结构声明为不可变的:
matchQueryService
这将与将所有字段标记为public readonly struct Pair
{
public int x;
public int y;
// Constructor and stuff
}
具有相同的效果,并且还将向编译器本身证明该结构是不可变的。通过减少编译器制作的防御性副本数量,这将提高使用该结构的区域的性能。
如Eric Lippert's answer中所述,这不会阻止结构本身被完全重新分配,从而提供其字段从您下方改变的效果。通过值传递或使用新的readonly
参数修饰符可以帮助防止这种情况:
in
答案 3 :(得分:1)
编译器将禁止分配给readonly
字段以及只读属性。
我建议使用只读属性主要用于公共接口和数据绑定(这对字段不起作用)。如果它是我的项目,我会要求如果struct / class是公共的。如果它将是一个程序集的内部或类的私有,我可以先忽略它,然后将它们重构为只读属性。