【问题标题】:Why there is two completely different version of Reverse for List and IEnumerable?为什么 List 和 IEnumerable 有两个完全不同的 Reverse 版本?
【发布时间】:2012-09-05 15:19:20
【问题描述】:

对于List 对象,我们有一个名为Reverse() 的方法。
它颠倒了“就地”列表的顺序,它不返回任何内容。

对于IEnumerable 对象,我们有一个名为Reverse() 的扩展方法。
它返回另一个 IEnumerable。

我需要逆序遍历一个list,所以不能直接用第二种方法,因为我得到了一个List,不想倒序,只能倒序遍历。

所以我可以这样做:

for(int i = list.Count - 1; i >=0; i--)

或者

foreach(var item in list.AsEnumerable().Reverse())

我发现它的可读性比我有一个 IEnumerable 时要差,就这样做

foreach(var item in list.Reverse())

我不明白为什么这两种方法以这种方式实现,名称相同。这很烦人和令人困惑。

为什么没有一个名为 BackwardsIterator() 的扩展来代替适用于所有 IEnumerable 的 Reverse()?

我对这个选择的历史原因非常感兴趣,而不是“如何做”!

【问题讨论】:

  • 你可以使用 Enumerable.Reverse(list)
  • 你为什么不把你的列表输入你喜欢的类型,然后它会使用你喜欢的方法的版本?
  • ^ 大概他“喜欢”它是List,因为它是List——但是,正如他明确表示的,他不想使用List.Reverse。 “typed to the type that you like”的意思是强制转换它,这并不比使用AsEnumerable()更漂亮。

标签: c# linq list ienumerable reverse


【解决方案1】:

值得注意的是,列表方法比扩展方法要老很多。命名可能与Reverse 保持相同,似乎比BackwardsIterator 更简洁。

如果你想绕过列表版本,去扩展方法,你需要把列表当成IEnumerable<T>

var numbers = new List<int>();
numbers.Reverse(); // hits list
(numbers as IEnumerable<int>).Reverse(); // hits extension

或者将扩展方法作为静态方法调用:

Enumerable.Reverse(numbers);

请注意,Enumerable 版本需要完全迭代底层可枚举,以便开始反向迭代。如果您打算对同一个可枚举项多次执行此操作,请考虑永久颠倒顺序并正常迭代。

【讨论】:

  • 实际上,即使一个人只想枚举一次并希望保持原始列表不受干扰,我希望调用ToArray() 然后调用几乎肯定会更快在结果数组上使用Reverse(),而不是使用Enumerable.Reverse()List&lt;T&gt;.ToArray() 方法可以将数据作为块存储在目标数组中,而无需处理单个项目,而 Enumerable.Reverse() 将——除非它对源为 List&lt;T&gt; 的情况进行特殊处理——必须复制项目在反转它们之前一次一个地放入一个数组中。
  • @Supercat 很有可能它有特殊的外壳。我过去曾在其他一些方法上使用过 ILSpy,它们在 IList 上很多时候都是特殊情况。
  • 特例确实很多,但也有一些恼人的皱纹。例如,尽管List&lt;Cat&gt; 可以传递给需要IEnumerable&lt;Animal&gt; 的代码,但这样的代码将无法利用任何特殊的“列表性”。我的“几乎可以肯定”的语言可能有点强,尽管在枚举之前转换为数组的一个优点是,如果我没记错的话,C# 在它知道是数组的东西上使用foreach 循环时会生成特殊代码。即使Enumerable.Reverse 返回临时数组的枚举数,编译器也不会知道。
  • @supercat 这绝对是一句好话,而且您还可以获得额外的好处,即不依赖当前 linq 实现的内部工作来获得任何性能提升。
  • 还有numbers.Reverse&lt;int&gt;(),是扩展的重载之一。
【解决方案2】:

然后编写你自己的 BackwardsIterator!

public static IEnumerable BackwardsIterator(this List lst)
{
    for(int i = lst.Count - 1; i >=0; i--)
    {
        yield return lst[i];
    }
}

【讨论】:

  • 如果你打算用它创建一个实用方法,你应该使用更有效的方法(即反向循环)。 Reverse 作为 IEnumerable 效率相当低,因为它需要迭代枚举,将其存储在缓冲区中,然后反向迭代缓冲区。
  • @Servy 你是对的..特别是如果列表是huge!..thx 指出它
【解决方案3】:

List&lt;T&gt;.Reverse 的存在早于IEnumerable&lt;T&gt;.Reverse 的存在。他们被命名相同的原因是......无能。这是一个可怕的烂摊子;显然 Linq IEnumerable&lt;T&gt; 函数应该被赋予一个不同的名称......例如,Backwards......因为它们具有完全不同的语义。事实上,它为程序员设置了一个可怕的陷阱——有人可能将list 的类型从List&lt;T&gt; 更改为,例如Collection&lt;T&gt;,然后突然list.Reverse();,而不是简单地反转list返回一个被丢弃的IEnumerable&lt;T&gt;。不能夸大 MS 给这些方法取同名的能力是多么的无能。

为避免该问题,您可以定义自己的扩展方法

public static IEnumerable<T> Backwards<T>(this IEnumerable<T> source) => source.Reverse();

您甚至可以添加一个特殊情况来高效处理可索引列表:

public static IEnumerable<T> Backwards<T>(this IEnumerable<T> source) =>
    source is IList<T> list ? Backwards<T>(list) : source.Reverse();

public static IEnumerable<T> Backwards<T>(this IList<T> list)
{
    for (int x = list.Count; --x >= 0;)
        yield return list[x];
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-08
    • 1970-01-01
    • 1970-01-01
    • 2022-06-13
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    相关资源
    最近更新 更多