编译器会优化对IEnumerable<T>.Count()的比较吗?

3

作为一个最基本的技巧,你经常听说要使用IEnumerable.Any()方法,因为这样不需要必须遍历整个可枚举对象。

我刚写了一小段代码来检查可枚举对象是否只包含一个元素或多个元素。

if (reportInfo.OrebodyAndPits.SelectMany(ob => ob.Pits).Count() > 1)
{
    ws.Cells[row, col++].Value = "Pits";
}
else
{
    ws.Cells[row, col++].Value = "Pit";
}

这让我想到,比较是否会编译成一种足够聪明的形式,只要枚举到第一个项之后就返回false呢?

如果不是,有没有一种方法可以编写linq扩展方法来实现这个功能呢?

(请注意,我对此代码片段的性能影响并不是特别感兴趣。我主要是好奇。)


2
不,.Count() 返回项目数量,例如 123456789,然后才检查 > 1 条件。对于智能行为,请使用 Skip(1).Any()。在某些情况下(本例除外),.Net 看到 IEnumerable<T> 实际上是 数组 T[]列表 List<T>,并调用 LengthCount 而不是遍历,但这就是我们所能期望的了。 - Dmitry Bychenko
2个回答

4
不行,它不会这样做。您的代码将计算序列中的所有项目。这是因为编译器不会对LINQ语句进行优化,所以您写的就是您得到的结果。
一个等价且更高效的检查序列是否包含多个项的方法是:
reportInfo.OrebodyAndPits.SelectMany(ob => ob.Pits).Skip(1).Any();

这将跳过第一个项目后,检查是否还有剩余的项目。

3

如果你想知道某个东西是如何工作的,为什么不看源代码呢?

这里是 Any() 方法:https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/AnyAll.cs#L20

这里是 Count() 方法:https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Count.cs#L12

编译器不能像你描述的那样进行优化。它请求计数并获得一个数字,然后将该数字与条件语句中的内容进行比较。

它确实尝试进行某种优化。正如你从 Count() 方法中看到的那样,它会尝试查看 IEnumerable 是否已经支持 Count 属性,并使用它,因为这比再次计算所有元素要快。如果不可用,它必须遍历整个集合并逐个计数。

如果你想编写一个LINQ方法(它只是一个在IEnumerable上的扩展方法),用于确定IEnumerable中是否至少有两个元素,那么这应该很容易。可以像这样编写:
例如:
    public static bool AtLeastTwo<TSource>(this IEnumerable<TSource> source)
    {
        if (source == null)
        {
            throw Error.ArgumentNull(nameof(source));
        }

        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            e.MoveNext(); // Move past the first one
            return e.MoveNext(); // true if there is at least a second element.
        }
    }

使用Skip + Any更简单,不需要扩展方法。 - Wazner
但这不是被问到的内容:“如果没有,有没有一种编写Linq扩展方法来实现这一点的方法?” - Colin Mackay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接