【问题标题】:Is there a practical reason why LINQ's LongCount extension method was added?添加 LINQ 的 LongCount 扩展方法是否有实际原因?
【发布时间】:2016-11-11 22:10:42
【问题描述】:

LINQ 有 2 种计算可枚举的方法:CountLongCount。实际上,这两者之间的唯一区别是第一个返回int,而第二个返回long

我不清楚为什么要添加第二种方法。似乎它唯一的用例是处理超过 2B 个元素的枚举。出于以下几个原因,这对我来说似乎是一个糟糕的决定:

  1. 大多数 BCL 集合都由一维数组支持,这些数组的长度保证适合 int。试图超越这将引发OverflowException / OutOfMemoryException

  2. LongCount 是 O(n),因为 IEnumerable 是惰性的。如果您有一个可枚举的 3B 元素,则在其上调用 LongCount,然后再次遍历它(如果您想使用任何值,则必须这样做),您将添加额外的 3B 迭代,这会非常慢,并且对开发者隐藏它。

  3. 其他 LINQ 操作,例如 ToArray / ToList,不支持具有 2B+ 元素的枚举,因为 (1)。

我在这里遗漏了什么,还是添加了LongCount 有更实际的原因?谢谢。

【问题讨论】:

  • 基于源代码LongCount 只是通过Enumerator.MoveNext 迭代IEnumerable,而Count 尝试将IEnumerable 转换为ICollection 并使用它的Count 如果转换不成功它将以与LongCount 相同的方式迭代IEnumerablehttps://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,d76b4b5d3fd67767。基于@EricLipert 的这个“猜测”似乎非常合乎逻辑
  • @Fabio,这只是一个优化。 LongCount 也检查 ICollection 并将结果转换为 long 在语义上是正确的,但 LongCount 的使用并不多,所以这就是他们没有费心这样做的原因。

标签: c# .net linq ienumerable linq-to-objects


【解决方案1】:

我对这个设计决策没有第一手知识,但我可以提供一个有根据的猜测。

该方法对IQueryable有明显的用处;查询可以很容易地得到一个巨大的数据库表的支持。

我希望

IQueryable<Foo> q = whatever;
long result1 = q.LongCount();
long result2 = q.AsEnumerable().LongCount();

产生相同的答案。要求内存中的查询使用返回不同类型的不同方法似乎是不恰当的,尤其是在实现可枚举版本非常容易的情况下。

但就像我说的,这是有根据的猜测;希望实际从事此设计工作的人可以加入。

【讨论】:

  • 昨天我打算回答这样的问题,直到我到达Enumerable.cs 中的LongCount 实现。它只是枚举从开始到结束的序列。我以为我会找到类似检查IEnumerable&lt;T&gt; 实现是否为ICollection&lt;T&gt; 并使用Count 或谁知道的东西。
  • 即使我们谈论某种由网络流支持的可枚举,到最后,LongCount 也会一一检查 长计数 i>.
  • @MatíasFidemraizer:这是不正确的。扩展 Eric Lippert 的庞大数据库表示例:非常频繁地查询数据库上的计数可以避免一一进行,而是依靠索引和元数据在亚线性时间内计算计数。一个较少面向数据库的示例是包含重复项的排序随机访问列表。通过运行稍微修改的二进制搜索,可以在 O(logn) 时间内获取计数。数据库可以根据索引执行类似的优化。
  • @Brian 我说是因为我检查了Enumerable.LongCount 源代码,它只是执行while 循环来累积给定序列中的项目数:\
  • 你说的都是对的。 LINQ-to-objects 实现只是进行简单的枚举和计数,不像Count 检查计数是否已知。 LINQ-to-SQL 实现将计数操作远程转移到数据库。
【解决方案2】:

我很确定它是为数据库查询引入的(例如,它应该为 sql server 查询生成 COUNT_BIG 而不是 COUNT),但是它可能在其他情况下有一些用途。例如假设我有这样的方法:

private static Random _r = new Random(1);
public static IEnumerable<BigInteger> RandomSequence(int upTo)
{
    while (true) {
        var next = _r.Next();
        if (next > upTo)
            yield break;
        yield return next;
    }
}

这个序列不是由任何数组烘焙的,也不会在任何地方存储值。因此,它可以轻松生产超过 2B 的物品。现在假设我想检查生成大于int.MaxValue - 5 的数字需要多少次迭代。如果我这样做:

RandomSequence(int.MaxValue - 5).Count();

它将因溢出异常而失败(因为Count 在内部很方便地在checked 区域中包装了增量)。但是LongCount 来救援!

RandomSequence(int.MaxValue - 5).LongCount();

现在我终于发现,使用种子 1,Random.Next 将在 2583066202 次迭代中产生大于 int.MaxValue - 5 的结果!

是的,示例有些人为,但仍然如此。

【讨论】:

    猜你喜欢
    • 2015-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-21
    • 1970-01-01
    • 2020-08-05
    • 1970-01-01
    相关资源
    最近更新 更多