【问题标题】:Does .Any with a predicate have to generate the resulting list before it looks for .Any带有谓词的 .Any 是否必须在查找 .Any 之前生成结果列表
【发布时间】:2017-11-16 15:54:43
【问题描述】:

我有一个包含 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 会导致 null 异常。
  2. 我认为 smallList 永远不会为空。它可能是一个 包含 0 个元素但不为空的列表。
  3. 使用谓词执行 .Any 意味着它必须生成列表,然后 确定它是否至少有 1 个元素,然后我的代码将有 再次生成相同的列表以将其分配给 smallList。

我说的对吗?我的团队负责人提议的更改是否基本上使创建此列表的工作量增加了一倍,而没有真正的好处?

【问题讨论】:

  • 首先,bigList 是什么?
  • bigList 是一个 List,它只是一个具有大约 50 个属性的对象。
  • Where 永远不会产生 null 作为输出(如果源集合为 null,它会抛出 NullReferenceException),只有空的 IEnumerable,如果调用 Tolist,它将转换为空列表
  • 您的团队负责人不正确,您的原始代码良好。
  • Where() 应该可以工作。 ToList() 在较大的物体上可能不是一个好主意。尽量使用IEnumerable 并限制您对列表的使用。

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


【解决方案1】:

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

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

只要有结果就停止源的枚举 确定。

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

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

我的团队负责人提议的更改是否基本上会增加一倍? 创建这个列表需要做多少工作却没有真正的好处?

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


.Where 可能会导致 null,调用 .ToList() 会导致 null 异常。

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

也许他很困惑,因为查询是在ToList 处执行的,所以如果查询中的某处有NullReferenceException,那么你会在ToList(或任何其他执行它的方法)看到这个异常.查看以下引发异常的查询:

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

【讨论】:

    【解决方案2】:

    他说.Where会导致null,而调用.ToList()会导致null异常。

    Where 可能导致空引用异常的唯一原因是它的目标是 null 或其谓词遇到空引用异常。如果没有匹配项,.Where 返回一个空的 IEnumerable&lt;T&gt;,而不是 null

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

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

    【讨论】:

      【解决方案3】:

      .Where() 应该只在 bigList 开头为 null 时引发异常。

      但是,执行.Any() 并不一定要生成列表。一旦找到与谓词匹配的第一个项,它将遍历一个集合并停止。

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

      【讨论】:

      • .Where 会产生异常,而不是null。至于ToList(),我们不知道遵循这个建议接下来会发生什么。
      • 这就是我的意思:/需要摩尔咖啡。
      • 哦,我支持我的 IEnumerable 评论。他可能有其他假定 List 的代码,但如果是这样,可能值得重构以允许 IEnumerable。 可能 List 在他的上下文中而不是 IEnumerable 中有意义,但那将是一个例外。很多时候,它只是假设你需要一个列表,而你真正做的只是在脚部表现方面射击自己。在这种情况下,由于可能存在大量项目,ToList() 可能会造成内存漏洞,因为它使用中间缓冲区来构建最终结果,最终会导致 OutOfMemoryExceptions。
      猜你喜欢
      • 1970-01-01
      • 2020-08-05
      • 1970-01-01
      • 1970-01-01
      • 2011-02-07
      • 2016-12-15
      • 2013-11-10
      • 2023-02-26
      • 1970-01-01
      相关资源
      最近更新 更多