ReadonlyCollection,对象是不可变的吗?

Posted

技术标签:

【中文标题】ReadonlyCollection,对象是不可变的吗?【英文标题】:ReadonlyCollection, are the objects immutable? 【发布时间】:2015-12-03 00:40:47 【问题描述】:

我正在尝试使用 ReadOnlyCollection 使对象不可变,我希望对象的属性是不可变的。

public ReadOnlyCollection<FooObject> MyReadOnlyList

    get
    
        return new ReadOnlyCollection<FooObject>(_myDataList);
    

但我有点困惑。

我尝试使用 foreach 将对象的属性更改为 MyReadOnlyList 并且...我可以更改 value 属性,是否正确?我理解 ReadOnlyCollection 设置了一个添加级别以使对象不可变。

【问题讨论】:

【参考方案1】:

ReadOnlyCollection 是不可变的事实意味着不能修改集合,即不能从集合中添加或删除任何对象。这并不意味着它包含的对象是不可变的。

This article 由 Eric Lippert 撰写,解释了不同类型的不变性是如何工作的。基本上,ReadOnlyCollection 是一个不可变的外观,它可以读取底层集合 (_myDataList),但不能修改它。但是,您仍然可以通过执行 _myDataList[0] = null 之类的操作来更改基础集合,因为您引用了 _myDataList

此外,ReadOnlyCollection 返回的对象与_myDataList 返回的对象相同,即this._myDataList.First() == this.MyReadOnlyList.First()(与LINQ)。这意味着如果_myDataList 中的对象是可变的,那么MyReadOnlyList 中的对象也是可变的。

如果您希望对象不可变,则应相应地设计它们。例如,您可以使用:

public struct Point

    public Point(int x, int y)
    
        this.X = x;
        this.Y = y;
    

    // In C#6, the "private set;" can be removed
    public int X  get; private set; 
    public int Y  get; private set; 

代替:

public struct Point

    public int X  get; set; 
    public int Y  get; set; 

编辑:在这种情况下,正如Ian Goldby 所指出的,这两个结构都不允许您修改集合中元素的属性。发生这种情况是因为结构是值类型,并且当您访问元素时,集合会返回该值的副本。如果 Point 是一个类,您只能修改它的属性,这意味着返回的是对实际对象的引用,而不是其值的副本。

【讨论】:

实际上,如果 ReadOnlyCollection 中的对象是结构而不是类,那么它们在集合中实际上是只读的,因为访问元素会给您一个 boxed 结构。你不能直接修改它(“不能修改拆箱转换的结果”),如果你将它分配给一个本地结构变量,你会得到一个副本。 (不过,您仍然可以通过 Items 属性修改 ReadOnlyCollection。) 如果集合是泛型并返回T 类型的元素,为什么集合返回的值会被装箱?除此之外,我更新了我的答案,说集合中的结构实际上是不可变的,因为您只能获取它们的值的副本。 我怀疑这是框架的优化,以防止在您读取结构时复制结构。当然,如果您写roList[i].X = 5,编译器会抱怨“无法修改拆箱转换的结果”。如果编译器不这样做,那么x = roList[i].X 将始终需要制作结构的完整副本以读取单个属性。 @IanGoldby “我怀疑这是框架的一种优化,可以防止在读取结构时复制结构。” - 你认为框架为什么这样做?我认为不会。 ReadOnlyCollection 使用索引器返回将返回结构副本的值。【参考方案2】:

我试图将对象的属性更改为 MyReadOnlyList 使用 foreach 和...我可以更改 value 属性,是否正确?一世 理解 ReadOnlyCollection 设置一个添加级别以使对象 不可变。

使用ReadOnlyCollection 并不能保证存储在集合中的对象。它只保证集合一旦被创建就不能被修改。如果从中检索到一个元素,并且它具有可变属性,则可以很好地对其进行修改。

如果您想让您的 FooObject 成为不可变的,那么只需这样做:

public class FooObject

    public FooObject(string someString, int someInt)
    
        SomeString = someString;
        SomeInt = someInt;
    

    public string SomeString  get; ;
    public int SomeInt  get; ;

【讨论】:

【参考方案3】:

不可变的是集合本身,而不是对象。目前,C# 不支持不可变对象而不像 ReadOnlyCollection&lt;T&gt; 在您的情况下那样包装它们。

好吧,如果它们的属性没有可访问的设置器,您仍然可以创建不可变对象。顺便说一句,它们根本不是不可变的,因为它们可以从可能具有与 setter 相同或更多可访问性的类成员发生变异。

// Case 1
public class A

    public string Name  get; private set; 

    public void DoStuff() 
    
        Name = "Whatever";
    


// Case 2
public class A

    // This property will be settable unless the code accessing it
    // lives outside the assembly where A is contained...
    public string Name  get; internal set; 


// Case 3
public class A

    // This property will be settable in derived classes...
    public string Name  get; protected set; 


// Case 4: readonly fields is the nearest way to design an immutable object
public class A

     public readonly string Text = "Hello world";

正如我之前所说,引用类型在定义上总是可变的,并且它们在某些条件下可以表现为不可变的成员可访问性。

最后,结构是不可变的,但它们是值类型,不应该仅仅因为它们可以表示不可变数据而使用它们。请参阅此问答以了解有关结构不可变的更多信息:Why are C# structs immutable?

【讨论】:

我在一本书中把它写成红色,但我认为它是错误的 >System.Collections.ObjectModel.ReadOnlyCollection 类型是包装集合并导出其只读版本的标准方法数据 @lunatic84 该定义说明了我的答案试图为您解决的问题。集合是不可变的,但数据仍然是可变的。

以上是关于ReadonlyCollection,对象是不可变的吗?的主要内容,如果未能解决你的问题,请参考以下文章

ReadOnlyCollection vs Liskov - 如何正确建模可变集合的不可变表示

只读(不可变)可序列化类

将 ReadOnlyCollection<byte> 写入流

readOnlycollection 的 .Item 属性不起作用

c# - 如何计算ReadOnlyCollection字段中字符串的出现次数[重复]

java中是啥是不可变对象和可变对象