C# 中数组的不可变替代方案是什么?

What are immutable alternatives for an array in C#?

C# 中生产代码的数组的不可变替代方案是什么(至少在 ImmutableArray 处于测试阶段时)?

您应该提供更多详细信息,但一个选项是 System.Collections.ObjectModel.ReadOnlyCollection<>,它充当 list/array。可以这样创建:

  var immutable1 = Array.AsReadOnly(new[] { 7, 9, 13, });
  var immutable2 = (new List<int> { 7, 9, 13, }).AsReadOnly();

它是 IList<> 的包装器。它不能转换为任何允许改变底层 list/array 的东西。但是底层对象当然可以通过反射到达。

如果有人在其他地方改变了 "inner" list/array,那将反映在 ReadOnlyCollection<> 包装器中。不执行复制。

这是一种 "shallow" 不变性形式。如果单个项目可以变异(int 不能),包装器不会禁止它。


一种更便宜的做法是只使用 IReadOnlyList<out T> 接口,它在 T 中是协变的(一个额外的优势)。 List<T>T[] 都实现了这个接口。所以:

  IReadOnlyList<int> immutable3 = new[] { 7, 9, 13, };
  IReadOnlyList<int> immutable4 = new List<int> { 7, 9, 13, };

然而,在那种情况下,消费者可能会测试 immutable3 as IList<int>,例如,或 immutable3 as int[],并获得一个 "mutable-type reference" 到可变对象(没有反射)。


但是,在Microsoft.Immutable.Collections中有不可变类型(例如ImmutableList<>)不是"in beta"。所以如果你真的需要它,你可以考虑安装它。它不是标准 .NET 库的一部分,因此您必须单独安装它。


为了详细说明 "shallow" 的意思,假设您有一个数组,其元素类型本身是可变的。然后无论你使用上面的哪一个建议,人们仍然可以修改条目。例如:

StringBuilder[] yourArray = { 
    new StringBuilder("All"),
    new StringBuilder("is"),
    new StringBuilder("good"),
    };

var immutable = ImmutableList<StringBuilder>.Empty.AddRange(yourArray);

immutable[0].Insert(0, "NOT ");
immutable[1].Clear();
immutable[2].Append(new string('!', 10000));

仅使用 ReadOnlyCollectionIReadOnlyList 的缺点是它们提供的保证比不可变数组弱得多。这些对象只保证只读集合的​​持有者不会修改它。他们不保证集合不会被包含对基础集合的引用的代码修改。

具体来说,此类对象的使用者在读取集合时仍必须使用线程同步,因为无法保证基础集合的所有者不忙于集合中的 adding/removing 个项目正在通过你的只读镜头阅读它。或者您必须在消费者和生产者之间有一个书面协议,即集合不会被修改。

如果您真的想要一个不可变数组并且由于某种原因不能使用不可变集合包,那么只需确保源永远不会修改数组,而是在需要进行更改时克隆它。结合分发 "read only" 句柄来实现不可变数组。

LINQ 可以很容易地实现你自己的,尽管它不是很聪明。以不可变方式删除数组中的一项与另一项的示例:

private int[] _array;

public void RemoveItem(int item)
{
    _array = _array.Where(x => x != item).ToArray();
}

/// <summary>Gets immutable array</summary>
public IReadOnlyList<int> Data { get { return _array; } }