不可变还是不可变?

好吧,据我所知, 不可变类型本质上是线程安全的,所以我在不同的地方读过,我想我明白为什么会这样。 如果在创建对象后无法修改实例的内部状态,则对实例本身的并发访问似乎没有问题。

因此,我可以创建以下List

 class ImmutableList: IEnumerable { readonly List innerList; public ImmutableList(IEnumerable collection) { this.innerList = new List(collection); } public ImmutableList() { this.innerList = new List(); } public ImmutableList Add(T item) { var list = new ImmutableList(this.innerList); list.innerList.Add(item); return list; } public ImmutableList Remove(T item) { var list = new ImmutableList(this.innerList); list.innerList.Remove(item); return list; } //and so on with relevant List methods... public T this[int index] { get { return this.innerList[index]; } } public IEnumerator GetEnumerator() { return innerList.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return ((System.Collections.IEnumerable)this.innerList).GetEnumerator(); } } 

所以问题是:这真的是一种不可改变的类型吗? 它真的是线程安全吗?

显然,类型本身是不可变的,但绝对没有保证T是,因此您可以直接与generics类型相关的并发访问和线程问题。 这是否意味着ImmutableList应该被认为是可变的

class ImmutableList: IEnumerable where T: struct是唯一真正被认为是不可变的类型吗?

感谢您对此问题的任何意见。

更新 :很多答案/评论都集中在我发布的ImmutableList的特定实现上,这可能不是一个很好的例子。 但问题的问题不在于实施。 我问的问题是, ImmutableList是否真的是一个不可变类型,考虑到不可变类型所需要的一切。

如果在创建对象后无法修改实例的内部状态,则对实例本身的并发访问似乎没有问题。

通常情况就是这样,是的。

这真的是一种不可改变的类型吗?

简而言之:你有一个围绕可变列表的写时复制包装器。 将新成员添加到不可变列表不会改变列表; 相反,它会创建底层可变列表的副本,添加到副本,并返回副本周围的包装器。

如果您正在包装的基础列表对象在读取时不会改变其内部状态,那么您已经满足了原来的“不可变”定义,所以,是的。

我注意到这不是一种实现不可变列表的非常有效的方法。 例如,对于不可变的平衡二叉树,您可能会做得更好。 每当你制作一个新的清单时,你的草图在时间和记忆中都是O(n); 你可以毫不费力地将其提高到O(log n)。

它真的是线程安全吗?

只要底层的可变列表对多个读者来说是线程安全的,是的。

您可能会对此感兴趣:

http://blogs.msdn.com/b/ericlippert/archive/2011/05/23/read-only-and-threadsafe-are-different.aspx

显然,类型本身是不可变的,但绝对没有保证T是,因此您可以直接与generics类型相关的并发访问和线程问题。 这是否意味着ImmutableList应该被认为是可变的?

这是一个哲学问题,而不是技术问题。 如果你有一个不可改变的人名列表,并且列表永远不会改变,但其中一个人死了,名字列表是“可变的”吗? 我想不会。

如果有关列表的任何问题始终具有相同的答案,则列表是不可变的。 在我们的人名列表中,“列表中有多少名字?” 是关于该列表的问题。 “这些人中有多少人还活着?” 这不是关于列表的问题,而是关于列表中提到的人的问题。 这个问题的答案随着时间而变化; 第一个问题的答案没有。

ImmutableList: IEnumerable where T: struct是真正被认为是不可变的唯一类型吗?

我没跟着你。 如何限制T成为一个结构改变什么? 好的,T仅限于struct。 我创建了一个不可变的结构:

 struct S { public int[] MutableArray { get; private set; } ... } 

现在我创建一个ImmutableList 。 什么阻止我修改存储在S实例中的可变数组? 仅仅因为列表是不可变的并且结构是不可变的不会使数组不可变。

不变性有时以不同的方式定义。 线程安全也是如此。

在创建一个目的是不可变的不可变列表时,您应该记录您正在做什么保证。 例如,在这种情况下,您保证列表本身是不可变的,并且没有任何隐藏的可变性(一些显然不可变的对象在幕后实际上是可变的,例如memoisation或内部重新排序作为优化),这消除了线程安全性来自不变性(尽管也可以以一种以不同方式保证线程安全的方式执行此类内部突变)。 您不能保证存储的对象可以以线程安全的方式使用。

您应记录的线程安全性与此相关。 您不能保证另一个对象不会具有相同的对象(如果您在每次调用时创建新对象,则可以)。 您可以保证操作不会破坏列表本身。

坚持使用T : struct可以提供帮助,因为这意味着你可以确保每次返回一个项目时,它都是结构的一个新副本( T : struct本身就不会这样做,因为你可能有这样做的操作’ t改变列表,但确实改变了它的成员,所以很明显你也必须这样做)。

这虽然限制了你不支持不可变引用类型(例如,在许多真实案例中往往是集合成员的string ),并且不允许用户使用它并提供自己的方法来确保包含的项目的可变性不会导致问题。 由于没有任何线程安全的对象可以保证它所使用的所有代码都是线程安全的,因此确保(尽可能多地帮助你,但不要试图确保你可以做到的’确保)。

它也不保护不可变列表中不可变结构的可变成员!

使用你的代码,让我说我这样做:

 ImmutableList mylist = new ImmutableList(); mylist.Add(1); 

…在StackOverflow上发布的代码会导致StackOverflow-Exception。 有很多明智的方法来创建线程保存集合,复制集合(至少尝试)并将它们称为不可变的,很多,并不是很有效。

Eric Lippert发布​​的链接可能非常值得一读。

数据类型的一个主要示例,其行为是可变对象的不可变列表:MulticastDelegate。 MulticastDelegate可以非常准确地建模为(对象,方法)对的不可变列表。 方法集和它们所作用的对象的身份是不可变的,但在绝大多数情况下,对象本身将是可变的。 实际上,在很多情况下(如果不是大多数情况下),委托的目的是改变它所持有引用的对象。

委托者不负责知道它要调用其目标对象的方法是否会以线程不安全的方式改变它们。 代表只负责确保其function和对象标识列表是不可变的,我认为没有人会同样期望它。

ImmutableList同样应始终保持T类型的同一组实例。 这些实例的属性可能会更改,但不会更改其身份。 如果创建了List ,其中包含两个Fords,序列号为#1234和#4422,两个都是红色的,其中一个Fords被涂成蓝色,那么最初的两个红色汽车的列表将更改为一张拿着蓝色汽车和一辆红色汽车的清单,但它仍会保留#1234和#4422。

我会说如果元素是可变的,generics列表不是不可变的,因为它不代表数据状态的完整快照。

要在您的示例中实现不变性,您必须创建列表元素的深层副本,这不是每次都有效。

您可以在IOG库中看到我对此问题的解决方案