不可变还是不可变?

InB*_*een 15 c# immutability

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

因此,我可以创建以下内容List:

class ImmutableList<T>: IEnumerable<T>
{
    readonly List<T> innerList;

    public ImmutableList(IEnumerable<T> collection)
    {
         this.innerList = new List<T>(collection);
    }

    public ImmutableList()
    {
         this.innerList = new List<T>();
    }

    public ImmutableList<T> Add(T item)
    {
         var list = new ImmutableList<T>(this.innerList);
         list.innerList.Add(item);
         return list;
    }

    public ImmutableList<T> Remove(T item)
    {
         var list = new ImmutableList<T>(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<T> GetEnumerator()
    {
        return innerList.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((System.Collections.IEnumerable)this.innerList).GetEnumerator();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Eri*_*ert 27

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

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

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

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

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

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

它真的是线程安全吗?

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

这可能是您感兴趣的:

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

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

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

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

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

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

struct S
{
    public int[] MutableArray { get; private set; }
    ...
}
Run Code Online (Sandbox Code Playgroud)

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


Jon*_*nna 5

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

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

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

坚持T : struct可能会有所帮助,因为这意味着你可以确保每次返回一个项目时,它都是结构的新副本(T : struct单独不会这样做,因为你可以进行不改变列表的操作,但是确实改变了它的成员,显然你也必须这样做).

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

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