带有谓词的 .Any 是否必须在查找 .Any 之前生成结果列表

Does .Any with a predicate have to generate the resulting list before it looks for .Any

我有一个包含 1,000,000 个复杂对象的列表。我需要使用这些对象的子集创建另一个列表,同时保持原始列表不变。在代码的这一点上,我确定 bigList 不为空,并且它至少有 1 个项目。

我的原码:

var smallList = bigList.Where(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount).ToList();

我的团队领导说我的代码有几个问题。他说 .Where 可能导致 null,而调用 .ToList() 会导致 null 异常。所以,为了避免这种情况,他说我需要将我的代码更改为:

var smallList = new List<CSVLines>();
if(bigList.Any(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount))
{
    smallList = bigList.Where(csvrec => csvrec.PreApprovalAmount <= 0 || csvrec.PreApprovalAmount > csvrec.ReplacementAmount).ToList();
}
  1. 我认为 .Where 不会导致空异常。
  2. 我认为 smallList 永远不会为空。这可能是一个 包含 0 个元素但不为空的列表。
  3. 用谓词做 .Any 意味着它必须生成列表,然后 确定它是否至少有 1 个元素,然后我的代码将有 再次生成相同的列表以将其分配给 smallList。

我说的对吗?我的团队领导提出的更改是否基本上使创建此列表的工作量增加了一倍而没有实际好处?

Does .Any with a predicate have to generate the resulting list before it looks for .Any

不,Enumerable.Any不需要那样做。 MSDN:

The enumeration of source is stopped as soon as the result can be determined.

该方法接受一个序列和一个谓词,然后枚举序列直到谓词匹配一次并且returns true。如果没有项目匹配 false 返回。因此,如果第一项已经匹配,则不需要枚举结果。在 Source-代码中:

foreach (TSource element in source) {
    if (predicate(element)) return true;
}
return false;

Are the proposed changes from my team lead basically doubling the amount of work to create this list with no real benefit?

是的,在这种情况下,首先检查是否有任何项目匹配,然后使用 Where 进行过滤是不必要的开销。它不会加倍开销,因为 Any 在第一次匹配时停止,但它是开销(如果没有匹配项,它会加倍,因为必须枚举序列两次)。


.Where could result in null, and that calling .ToList() would cause a null exception.

不,那是不可能的。 Enumerable.Where 从不 returns null,它是对输入序列的过滤器,如果没有项目匹配谓词 Enumerable.Empty<T> 则返回。

也许他很困惑,因为查询是在 ToList 处执行的,因此如果查询中某处有 NullReferenceException,那么您会在 ToList 处看到此异常(或任何其他执行它的方法)。查看以下抛出异常的查询:

var query = "foo".Select(c => { throw new NullReferenceException(); return 1; });
List<int> list = query.ToList(); // exception here not in first line

He said that .Where could result in null, and that calling .ToList() would cause a null exception.

Where 可能导致空引用异常的唯一原因是它的目标是 null 或者它的谓词遇到空引用异常。如果没有匹配项,.Where returns 一个空的 IEnumerable<T>,而不是 null.

添加对 Any 的调用是完全多余的。虽然它没有重新创建整个列表,但它在返回 truefalse 之前仍然使用一些 CPU 循环来评估谓词。尽管如此,它仍然是 O(n),所以当没有找到匹配项时,将针对整个序列评估条件。

添加对 Any 的调用也会降低代码的可读性,这可以说比浪费 CPU 个周期更糟糕。

.Where() 仅当 bigList 开头为空时才应导致异常。

但是,执行 .Any() 不必生成列表。它将遍历一个集合,并在找到 第一个 匹配谓词的项目时立即停止。

您可能应该做的一项更改是删除 ToList() 调用。尽量坚持使用 IEnumerable 并避免创建 List 对象,尤其是在处理较大的集合时。这是为了改善内存使用并避免导致 OutOfMemoryExceptions 而不是 NullReferenceExceptions。