【问题标题】:What's the best way of creating a readonly array in C#?在 C# 中创建只读数组的最佳方法是什么?
【发布时间】:2011-01-09 04:04:09
【问题描述】:

我遇到了极不可能的原始情况,即想从我的财产中返回一个只读数组。到目前为止,我只知道一种方法——通过System.Collections.ObjectModel.ReadOnlyCollection<T>。但这对我来说似乎有点尴尬,更不用说这个类失去了通过索引访问数组元素的能力补充说:哎呀,我错过了索引器)。没有更好的办法吗?可以使数组本身不可变的东西?

【问题讨论】:

  • ReadOnlyCollection 有一个索引器,它支持索引。你不能使数组不可变。
  • 其实,ReadOnlyCollection确实有一个 indexer 属性...
  • @BFree - 但(重要的是)它不会让您通过索引器成功重新分配项目
  • @nobugz - 当然可以:new string[0] - 很多不可变的;-p
  • 两点:(1)我们正在考虑为未来的C#版本使用不可变数组,但这很棘手;如果无法更改数组的内容,如何初始化数组?我们正在努力,但没有承诺,这只是在构思阶段。并且(2)不要忘记只读集合只是可变对象的不可变外观;如果其他人对底层对象进行了变异,外观将暴露变异。

标签: c# arrays readonly


【解决方案1】:

使用ReadOnlyCollection<T>。它是只读的,而且与您想象的相反,它有一个索引器。

数组不是不可变的,如果不使用像 ReadOnlyCollection<T> 这样的包装器,就无法制作它们。

请注意,创建 ReadOnlyCollection<T> 包装器是 O(1) 操作,不会产生任何性能成本。

更新
其他答案建议将集合转换为较新的IReadOnlyList<T>,它扩展IReadOnlyCollection<T> 以添加索引器。不幸的是,这实际上并不能让您控制集合的可变性,因为它可能会被强制转换回原始集合类型并发生变异。

相反,您仍应使用ReadOnlyCollection<T>List<T> 方法AsReadOnly(),或Arrays 静态方法AsReadOnly() 有助于相应地包装列表和数组)来创建对集合的不可变访问,并然后直接或作为它支持的任何一种接口公开它,包括IReadOnlyList<T>

【讨论】:

  • ReadOnlyCollection 不能用作不可变集合。它比数组略less,但周围有more 代码。唯一的“功能”是不允许其用户修改集合,但它甚至不向用户保证...底线:没用!
  • @gatopeich:无用是毫无根据的说法。请提供您的来源,说明它没有被使用。不过,不错的咆哮。
  • 我希望这个类可以实现 IReadOnlyList 接口。
  • 另见@Warty 在 .NET 4.5 中对IReadOnlyList<T> 的回答。
  • @Mike-EEE:在小型阵列上,它似乎确实慢了约 10 倍。但是,在较大的阵列上,它的速度要慢约 2 倍。不理想,我同意,虽然我不得不想知道你在做什么,这意味着性能在这种情况下很重要。我刚做了1亿次迭代测试,不到一秒。
【解决方案2】:

.NET Framework 4.5 引入了IReadOnlyList<T>,它从IReadOnlyCollection<T> 扩展而来,添加了T this[int index] { /*..*/ get; }

您可以从T[] 投射到IReadOnlyList<T>。这样做的一个优点是(IReadOnlyList<T>)array 可以理解地等于array;不涉及拳击。

当然,由于没有使用包装器,(T[])GetReadOnlyList() 将是可变的。

【讨论】:

  • 正如您所指出的,仅转换到界面的问题是它可以再次转换回来。这意味着您无法控制数据的可变性。您仍然应该先使用ReadOnlyCollection<T> 进行包装,然后再使用IReadOnlyList<T>,以便您可以控制数据的使用方式。
  • 我认为这归结为合同:如果我返回一个对象,那么我已经允许你使用该对象公开的任何方法。例如,如果您决定使用反射来窥视和操纵该对象的内部结构,那么所有保证都将丢失。同样,如果我返回一个 IEnumerable,那么我就授予你枚举结果的权利。如果您决定直接将 IEnumerable 强制转换为 List 并清除它(阅读:(List<T>)result 而不是 result.ToList()),那么您正在处理我尚未定义的行为。
  • 请注意,遍历 this 比遍历数组慢了近十倍。
  • 更新:在 .NET Core 中现在有 ReadOnlySpan,它可能会比上面的性能更高。不过,我还没有机会使用它。
【解决方案3】:

从 .NET Framework 2.0 起,Array.AsReadOnly 会自动为您创建一个 ReadOnlyCollection 包装器。

【讨论】:

    【解决方案4】:

    如果你真的想返回一个数组,但又怕数组的消费者会弄乱内部数据,那么就返回一个数组的副本。我个人仍然认为ReadOnlyCollection<T> 是要走的路,但如果你真的想要一个数组.....

    【讨论】:

    • 似乎是个好主意,直​​到您意识到 class.MyArray[i] 将在循环中执行什么操作。这样做 1000 次,你就有了 1000 个数组副本!
    • @RichardOD 是的,但你不要那样做。如果您通过主类上的索引访问它,那么您只需返回元素。如果您要返回数组,则为用户提供一个函数,该函数将返回数组的副本,并对其进行迭代。
    【解决方案5】:

    我知道这是一个老问题,但现在有一些适用于 .NET 的包包含 ImmutableArray 类型。内置于 .NET Core 3,可通过 NuGet 获取 Full framework 4.5 及更高版本

    【讨论】:

      【解决方案6】:

      我想到了 IEnumerable。

      【讨论】:

      • 您的意思是将我的数组转换为 IEnumerable 并返回吗?那将比 ReadOnlyCollection 更糟糕。首先,任何人都可以轻松地将其转换回数组并对其进行修改。其次,它提供了数组提供的功能的一个更小的子集。
      • 您可以使用迭代器(yield return 语句)使这项工作正常工作。
      【解决方案7】:

      您可能希望实现 IEnumerable 接口并重载 this[int] 运算符以拒绝对其设置器的访问

      【讨论】:

      • 说什么?它比上面提到的 ReadOnlyCollection 有什么好处?
      • 翻译:“你可能想实现一个接口但没有实现它”-1
      【解决方案8】:

      现在支持不可变集合。 见https://www.nuget.org/packages/System.Collections.Immutable

      这支持以下任何一种:

      • .NET 4.5(或更高版本)
      • .NETStandard 1.0(或更高版本)
      • Windows 8.0
      • WindowsPhone 8.0
      • WindowsPhoneApp 8.1
      • 可移植类库(.NETFramework 4.5、Windows 8.0、WindowsPhone 8.0、WindowsPhoneApp 8.1)

      【讨论】:

        猜你喜欢
        • 2010-09-05
        • 2012-11-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-07
        • 2013-06-18
        • 1970-01-01
        • 2011-09-16
        • 2017-08-06
        相关资源
        最近更新 更多