如何创建不可变的类?


113

我正在创建一个不可变的类。
我已将所有属性标记为只读。

我在班上有一个项目清单。
尽管如果该属性为只读,则可以修改列表。

公开列表的IEnumerable使其不可变。
我想知道使一类不变的基本规则是什么?


2
我强烈建议您阅读Eric Lippert关于不变性的博客系列,尤其是有关某种不变性”的条目。您对“公开列表的IEnumerable使其不可变”的评论对我来说有些奇怪。那是什么意思
乔恩·斯基特

我的意思是,而不是允许访问列表(如果该对象具有其他一些对象的列表),则允许用户使用IEnumerable访问成员。此处的谈话清单如特定示例所示,但可以是任何数据结构。
Biswanath

1
当MSDN存档这些博客时,Jon Skeet与Eric Lippert的博客系列的链接已断开。请参阅“某种不变性”。该系列的其余部分似乎在2007年12月+ 2008年1月的左侧面板中。
John Doe

请参阅埃里克利珀的人们通常如何混淆的条款博客系列atomicityvolatilityimmutability第一部分第二部分第三部分。这些来自他的个人博客,而且我相信比他的MSDN帖子更适合新手。
John Doe

Answers:


121

我认为您在正确的轨道上-

  • 注入类的所有信息都应在构造函数中提供
  • 所有属性只能是吸气剂
  • 如果将集合(或数组)传递给构造函数,则应将其复制以防止调用方稍后对其进行修改
  • 如果您要返回集合,则返回副本或只读版本(例如,使用ArrayList.ReadOnly或类似版本),可以将其与上一点结合使用,并存储只读副本,以在返回时调用者访问它),返回一个枚举数或使用其他允许对集合进行只读访问的方法/属性
  • 请记住,如果任何成员都是可变的,那么您可能仍然会出现可变类的外观-如果是这种情况,则应复制要保留的任何状态,并避免返回整个可变对象,除非您复制它们在将它们返回给调用者之前-另一个选择是仅返回可变对象的不可变“部分”-感谢@Brian Rasmussen鼓励我扩展这一点

我是否应该编写包装查找属性,该属性只允许您进行查找?并感谢您的回答。
Biswanath

5
任何作为参数传递给构造函数的可变引用类型都应该被复制。否则,调用方仍将保留对该状态的引用。
布赖恩·拉斯穆森

@Brian Rasmussen,您是对的,但是即使复制了它,任何调用者也可能可以访问可变对象,具体取决于提供的acces.sors。如果要传递可变对象,则最好始终返回该对象的不同副本或不变部分的类
Blair Conrad

@Blair-如果我在类中有字典,我只想使用它进行查找。查找的只读属性是否可以?
比斯瓦纳斯

@Biswanath-是的,需要警告的是,如果字典中的值是可变的,则如果不希望它们发生突变,则需要在返回之前保护它们
Blair Conrad

18

为了不可变,所有属性和字段都应为只读。并且任何列表中的项目本身应该是不可变的。

您可以如下创建一个只读列表属性:

public class MyClass
{
    public MyClass(..., IList<MyType> items)
    {
        ...
        _myReadOnlyList = new List<MyType>(items).AsReadOnly();
    }

    public IList<MyType> MyReadOnlyList
    {
        get { return _myReadOnlyList; }
    }
    private IList<MyType> _myReadOnlyList

}

1
我认为ImmutableList会更好。
Kevin Wong

使用ImmutableList,也可以考虑密封类
kofifus

我不认为ImmutableList适合此用例:basic rules to make a class (that contains a list) immutable。当将项目添加到列表的副本时,ImmutableList语义可实现更有效的内存使用,但是在我看来,这是一个更具体的用例。

11

另外,请记住:

public readonly object[] MyObjects;

即使标记了readonly关键字也不是不变的。您仍然可以通过索引访问器更改单个数组的引用/值。


5

使用ReadOnlyCollection该类。它位于System.Collections.ObjectModel名称空间中。

在返回列表的任何内容上(或在构造函数中),将列表设置为只读集合。

using System.Collections.ObjectModel;

...

public MyClass(..., List<ListItemType> theList, ...)
{
    ...
    this.myListItemCollection= theList.AsReadOnly();
    ...
}

public ReadOnlyCollection<ListItemType> ListItems
{
     get { return this.myListItemCollection; }
}

1

另一种选择是使用访问者模式,而不是完全公开任何内部集合。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.