使用不可变结构的公共只读字段是否有效?

时间:2011-05-19 18:28:49

标签: c# struct immutability

这是声明不可变结构的正确方法吗?

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
}

(旁白:我明白使用属性更具概括性并且允许更改,但是这个结构只是为了存储两个值。我只是对这里的不变性问题感兴趣。)

4 个答案:

答案 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是公共的。如果它将是一个程序集的内部或类的私有,我可以先忽略它,然后将它们重构为只读属性。