为什么对.Select(...)。Last()进行优化,而对.Select(...)。Last(...)进行优化?

时间:2019-01-04 08:24:18

标签: c# .net linq

考虑以下枚举器:

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

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

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

items.Last();
inspect 5

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

items.Last(x => true);
inspect 1
inspect 2
inspect 3
inspect 4
inspect 5

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

另一方面:

这说明了如何未优化回调案例。但这并不能解释为什么

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

似乎也不是很难实现:据我所知,它所需要的只是IPartition<T>上的一个附加方法。

缺乏优化也可能令人惊讶。由于这些重载使用相同的名称,因此可以假定它们也以类似的方式进行了优化。 (至少我就是这么想的。)

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

1 个答案:

答案 0 :(得分:1)

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

  

从概念上讲,如果至少一个元素满足谓词(可能在   实践),然后向后迭代可能会导致退出循环较早。

对于Last(Func<T,bool>)的一般实现而言,这个假设太过复杂了。通常,您不能假设满足谓词的最后一个元素更接近集合的末尾。该 optimization 对于您的示例(Last(x=>true)来说效果很好,但是对于每个这样的示例,都有一个相反的示例,其中 optimization 的效果较差({{1} }。