.NET 中可以使用不可变数组吗?

问题描述 投票:0回答:10

是否可以以某种方式将

System.Array
标记为不可变。当放在 public-get/private-set 后面时,它们无法添加到其中,因为它需要重新分配和重新分配,但消费者仍然可以设置他们想要的任何下标:

public class Immy
{
    public string[] { get; private set; }
}

我认为

readonly
关键字可能会起作用,但没有这样的运气。

c# .net arrays immutability
10个回答
29
投票

框架设计指南建议返回数组的副本。这样,消费者就无法更改数组中的项目。

// bad code
// could still do Path.InvalidPathChars[0] = 'A';
public sealed class Path {
   public static readonly char[] InvalidPathChars = 
      { '\"', '<', '>', '|' };
}

这些更好:

public static ReadOnlyCollection<char> GetInvalidPathChars(){
   return Array.AsReadOnly(InvalidPathChars);
}

public static char[] GetInvalidPathChars(){
   return (char[])InvalidPathChars.Clone();
}

这些例子直接来自书中。


27
投票

ReadOnlyCollection<T>
可能就是您正在寻找的。它没有
Add()
方法。


14
投票

请参阅基类库中的不可变集合现已可用


8
投票

可以使用Array.AsReadOnly方法返回。


2
投票

我认为最佳实践是在公共 API 中使用

IList<>
而不是数组,正是出于这个原因。 readonly 将阻止在构造函数之外设置成员变量,但正如您所发现的,不会阻止人们在数组中分配元素。

请参阅 Eric Lippert 的文章数组被认为有些有害了解更多信息。

编辑: 数组不能是只读的,但可以通过

Array.AsReadOnly()
将它们转换为只读 IList 实现,正如@shahkalpesh 指出的那样。


0
投票

唯一要添加的是数组意味着可变性。当您从函数返回数组时,您是在向客户端程序员建议他们可以/应该更改一些内容。


0
投票

根据 Matt 的回答,IList 是一个完整的数组抽象接口,因此它允许添加、删除等。我不确定为什么 Lippert 似乎建议它作为需要不变性的 IEnumerable 的替代品。 (编辑:因为 IList 实现可以为那些变异方法抛出异常,如果你喜欢这种事情的话)。

也许要记住的另一件事是列表中的项目也可能具有可变状态。如果您确实不希望调用者修改此类状态,您有一些选择:

确保列表中的项目是不可变的(如您的示例中:字符串是不可变的)。

返回所有内容的深度克隆,因此在这种情况下您无论如何都可以使用数组。

返回一个提供对项目的只读访问权限的接口:

interface IImmutable
{
    public string ValuableCustomerData { get; }
}

class Mutable, IImmutable
{
    public string ValuableCustomerData { get; set; }
}

public class Immy
{
    private List<Mutable> _mutableList = new List<Mutable>();

    public IEnumerable<IImmutable> ImmutableItems
    {
        get { return _mutableList.Cast<IMutable>(); }
    }
}

请注意,从 IImmutable 接口访问的每个值本身都必须是不可变的(例如字符串),或者是您即时创建的副本。


0
投票

您能做的最好的事情就是扩展现有的集合来构建您自己的集合。最大的问题是它的工作方式必须与每个现有集合类型不同,因为每次调用都必须返回一个新集合。


0
投票

.NET 倾向于避开数组,除了最简单和最传统的用例之外。对于其他一切,有各种可枚举/集合实现。

当您想要将一组数据标记为不可变时,您将超出传统数组提供的功能。 .NET 提供了同等的功能,但技术上不是以数组的形式。要从数组中获取不可变集合,请使用

Array.AsReadOnly<T>
:

var mutable = new[]
{
    'a', 'A',
    'b', 'B',
    'c', 'C',
};

var immutable = Array.AsReadOnly(mutable);

immutable
将是一个
ReadOnlyCollection<char>
实例。作为更通用的用例,您可以从任何通用
ReadOnlyCollection<T>
实现创建
IList<T>

var immutable = new ReadOnlyCollection<char>(new List<char>(mutable));

请注意,它必须是通用实现;普通的旧

IList
不起作用,这意味着您不能在传统数组上使用此方法,传统数组仅实现
IList
。这揭示了使用
Array.AsReadOnly<T>
作为访问通常无法通过传统数组访问的通用实现的快速方法的可能性。

ReadOnlyCollection<T>
将使您能够访问不可变数组所期望的所有功能:

// Note that .NET favors Count over Length; all but traditional arrays use Count:
for (var i = 0; i < immutable.Count; i++)
{
    // this[] { get } is present, as ReadOnlyCollection<T> implements IList<T>:
    var element = immutable[i]; // Works

    // this[] { set } has to be present, as it is required by IList<T>, but it
    // will throw a NotSupportedException:
    immutable[i] = element; // Exception!
}

// ReadOnlyCollection<T> implements IEnumerable<T>, of course:
foreach (var character in immutable)
{
}

// LINQ works fine; idem
var lowercase =
    from c in immutable
    where c >= 'a' && c <= 'z'
    select c;

// You can always evaluate IEnumerable<T> implementations to arrays with LINQ:
var mutableCopy = immutable.ToArray();
// mutableCopy is: new[] { 'a', 'A', 'b', 'B', 'c', 'C' }
var lowercaseArray = lowercase.ToArray();
// lowercaseArray is: new[] { 'a', 'b', 'c' }

0
投票

是的,您现在可以将
ImmutableArray
与 .NET Core 1.0+ 一起使用

using System.Collections.Immutable;

var arr1 = new [] {1,2,3}.ToImmutableArray();
var arr2 = ImmutableArray.Create(new [] {1,2,3});

DotNetFiddle 中的演示

另请参阅欢迎 ImmutableArray 作者:Immo Landwerth

© www.soinside.com 2019 - 2024. All rights reserved.