为什么`.Select(...).Last()`优化,但是`.Select(...).Last(...)`不?

Lam*_*iry 15 .net c# linq

考虑以下枚举器:

var items = (new int[] { 1, 2, 3, 4, 5 }).Select(x =>
{
    Console.WriteLine($"inspect {x}");
    return x;
});
Run Code Online (Sandbox Code Playgroud)

这会产生元素[1, 2, 3, 4, 5],在消耗时打印它们.

当我Last在这个枚举器上调用该方法时,它会触发一个只访问单个元素的快速路径:

items.Last();
Run Code Online (Sandbox Code Playgroud)
inspect 5
Run Code Online (Sandbox Code Playgroud)

但是当我将回调传递给Last它时,它从头开始遍历整个列表:

items.Last(x => true);
Run Code Online (Sandbox Code Playgroud)
inspect 1
inspect 2
inspect 3
inspect 4
inspect 5
Run Code Online (Sandbox Code Playgroud)

浏览.NET Core源代码,我发现:

另一方面:

这解释了如何不优化回调情况.但它没有解释原因.

从概念上讲,如果至少有一个元素满足谓词(可能在实践中),则向后迭代可以允许提前退出循环.

它似乎也难以实现:从我所看到的,所需要的只是一个额外的方法IPartition<T>.

缺乏优化也可能令人惊讶.由于这些重载具有相同的名称,因此可以假设它们也以类似的方式进行优化.(至少那是我的想法.)

鉴于优化此案例的这些原因,为什么LINQ的作者选择不这样做呢?

Mus*_*ata 2

Last()始终可以针对允许在恒定时间 ( ) 内访问集合的最后一个元素的集合进行优化O(1)。对于这些集合,您可以直接访问最后一个元素,而不是迭代所有集合并返回最后一个元素。

从概念上讲,如果至少一个元素满足谓词(这在实践中很可能),则向后迭代可能允许提前退出循环。

对于 的通用实现来说,这个假设太牵强了Last(Func<T,bool>)。一般来说,您不能假设满足谓词的最后一个元素更接近集合的末尾。该优化对于您的示例效果很好 ( Last(x=>true)),但对于每个这样的示例,都可能存在一个相反的示例,其中该优化性能较差 ( Last(x=>false))。

  • “*您不能假设满足谓词的最后一个元素通常更接近集合的末尾*”,这并不是真正的重点。找到向前移动的最后一个元素意味着无论如何你都必须查看所有元素 (9认同)