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<T>
在您的情况下那样包装它们。
好吧,如果它们的属性没有可访问的设置器,您仍然可以创建不可变对象。顺便说一句,它们根本不是不可变的,因为它们可以从可能具有与 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以上是关于ReadonlyCollection,对象是不可变的吗?的主要内容,如果未能解决你的问题,请参考以下文章
ReadOnlyCollection vs Liskov - 如何正确建模可变集合的不可变表示
将 ReadOnlyCollection<byte> 写入流
readOnlycollection 的 .Item 属性不起作用