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

时间:2017-11-16 15:54:43

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

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

我的原始代码:

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

我的团队负责人说我的代码存在一些问题。他说。可能会导致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. 我不这么认为。可能会导致空例外。
  2. 我认为smallList永远不会为null。它可能是一个 list包含0个元素,但不是null。
  3. 使用谓词执行。任何意味着它必须生成列表 确定它是否至少有1个元素,然后我的代码将具有 再次生成相同的列表以将其分配给smallList。
  4. 我说错了吗?我团队提出的修改建议是否基本上使创建此列表的工作量增加一倍而没有真正的好处?

3 个答案:

答案 0 :(得分:5)

  

带谓词的.Any必须先生成结果列表   它寻找.Any

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

  

只要结果可以,就会停止枚举源   确定。

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

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

我团队的建议变更是否基本上加倍了   创建此列表的工作量没有真正的好处吗?

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

  

.Where可能导致null,并且调用.ToList()会导致   null例外。

不,那是不可能的。 Enumerable.Where永远不会返回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

答案 1 :(得分:3)

  

他说{​​{1}}可能导致.Where,而调用null会导致空例外。

.ToList()可能导致空引用异常的唯一原因是它的目标是Where还是其谓词遇到空引用异常。如果没有匹配,null会返回空的.Where,而不是IEnumerable<T>

添加对null的通话完全是多余的。虽然它不会重新创建整个列表,但在返回Anytrue之前,它仍会在评估谓词时使用一些CPU周期。但它仍然是O(n),因此当找不到匹配时,将评估整个序列的条件。

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

答案 2 :(得分:0)

.Where()只会在bigList为空时才会导致异常。

但是,执行.Any()不必生成列表。只要找到与谓词匹配的 first 项,它就会经过一个集合并停止。

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