LINQ 使用 Skip 进行数量判断提速

当我们需要判断集合是否有元素时,很容易想到:

list.Count() > 0

改成:

list.Any()

因为 Any() 只需要确认有没有第一个元素,找到之后就可以停止遍历。

但是,当需求变成“满足复杂条件的元素是否超过 5 个”时,很多人首先想到的还是:

list.Count(x => ComplexCondition(x)) > 5

这段代码当然没有问题,但它会把整个序列遍历完,计算出满足条件的元素总数。实际上,我们只想知道有没有第 6 个元素,并不关心最终到底是 6 个、60 个,还是 6000 个。

这时可以使用 WhereSkipAny

bool result = list
    .Where(x => ComplexCondition(x))
    .Skip(5)
    .Any();

这段代码的意思是:先筛选满足条件的元素,跳过前 5 个,再判断后面是否还有元素。如果跳过 5 个以后仍然有元素,就说明至少存在第 6 个匹配项,也就是数量大于 5。

为什么可以提速

Count(predicate) 需要遍历完整个序列,才能得到准确数量:

list.Count(predicate) > 5;

Where(predicate).Skip(5).Any() 利用了 LINQ 的延迟执行。它会逐个检查元素,在找到第 6 个匹配项后立即返回 true,不再继续处理后面的元素。

当集合较大、匹配项较多,或者条件判断本身比较耗时时,提前结束遍历就可能带来明显收益。如果匹配项不超过 5 个,两种写法通常都需要遍历完整个序列。

Skip 的数量怎么写

可以记住下面的规律:

// 判断匹配数量是否大于 n
source.Where(predicate).Skip(n).Any();

// 判断匹配数量是否至少为 n(n > 0)
source.Where(predicate).Skip(n - 1).Any();

例如:

// 大于 5 个:确认第 6 个是否存在
source.Where(predicate).Skip(5).Any();

// 至少 5 个:确认第 5 个是否存在
source.Where(predicate).Skip(4).Any();

需要注意的地方

  • 如果只是判断 List<T> 的总数量,直接使用 list.Count 属性即可;本文讨论的是带筛选条件的数量判断。
  • 谓词最好是无副作用的纯判断,因为 Any() 可能提前结束,谓词不一定会对所有元素执行。
  • 对 EF Core 等 IQueryable 查询,最终性能还取决于生成的 SQL、索引和执行计划,必要时应结合真实数据测试。