是包含被视为不可变的可变引用类型成员的值类型

本文关键字:引用类型 成员 类型 不可变 包含 | 更新日期: 2023-09-27 17:50:12

我知道可变结构体是邪恶的。但是考虑以下struct:

public struct Foo
{
    private readonly StringBuilder _sb;
    public Foo(StringBuilder sb)
    {
        _sb = sb;
    }
    public StringBuilder Sb
    {
        get { return _sb; }
    }
}

对我来说,Foo似乎不可变,因为当你做var foo = new Foo(new StringBuilder())时,你不能重新分配Sb属性,因为它是只读的。

但是你可以这样做:

var foo = new Foo(new StringBuilder());
foo.Sb.Append("abc"); // _sb is also modified

会影响foo的内部状态。

问题:这是否被认为是一个好的struct设计,或者我应该避免在值类型中使用可变引用类型成员?

是包含被视为不可变的可变引用类型成员的值类型

这就是所谓的浅不变性。

看一下Joe Duffy的《解决多线程代码中的11个可能的问题》(重点是我的)中的不可变性部分:

例如,一个只有只读字段的。net类型是浅不可变。(…)更进一步,如果每个字段本身都引用了另一个类型,该类型的字段都是只读的(并且只引用深度不可变类型),那么该类型是深度不可变。这就保证了整个对象图不会在你下面发生变化,这是非常有用的。

如果你让你的结构不可变是为了防止客户端试图修改作为参数传递的结构的副本,而不是原始值,那么浅不可变是可以的。原值和副本值都指向同一个StringBuilder

然而,如果你这样做是为了同步的目的,那么它就不会有真正的帮助。

摘自Eric Lippert的《c#中的不变性第一部分:各种不变性》:

真正深度不可变的对象有很多很棒的属性。例如,它们是100%线程安全的,因为显然在读取器和(不存在的)写入器之间不会有冲突。它们比可以变化的物体更容易推理。但是他们严格的要求可能超出了我们的需要,或者超出了实际实现的范围。

你的设计规则取决于你是否有能力让你的选择被你和其他人清楚地理解。

下面是我的例子:我尽量保持我的结构包含简单的复合元素,如Vectors, Points,并且只使用不应该单独更改的值类型。

如果我需要在结构体内部使用集合或引用类型,这些结构体暴露了可能改变其状态的成员,我将其设置为私有,并将函数方法包装起来以从中获取内容。因为我必须防止外界对它进行修改。

如果struct中的某些东西必须被修改,我认为struct不是我需要的。

同样,没有什么可以阻止您通过使用像这样的浅不变性来欺骗结构。Dcastro的回答让我明白了这是怎么回事。

然而,

因为结构体不是按照这种方式设计的,所以它可能会让人感到困惑。

我喜欢Eric Lippert的这篇文章,它让我想起了结构体应该是如何工作的。