为什么当我将代码从 IEnumerable 更改为列表时,它有效

本文关键字:列表 有效 代码 IEnumerable 为什么 | 更新日期: 2023-09-27 18:35:30

我试图解决这个问题,而不仅仅是把它归结为一般的巫毒教。

我执行 EF 查询并获取一些数据,然后.ToList()它,如下所示:

IEnumerable<DatabaseMatch<CatName>> nameMatches = nameLogicMatcher.Match(myIQueryableOfCats).ToList();

有些猫在

数据库中出现两次,因为它们有多个名字,但每只猫都有一个主要名称。因此,为了过滤掉它,我在一个列表中获取了猫的所有ID:

List<int> catIds = nameMatches.Select(c => c.Match.CatId).ToList();

然后,我遍历所有不同的 id,获取所有匹配的猫名,并从列表中删除任何不是主要名称的内容,如下所示:

foreach (int catId in catIds.Distinct())
{
    var allCatNameMatches = nameMatches.Where(c => c.Match.CatId == catId);
    var primaryMatch = allCatNameMatches.FirstOrDefault(c => c.Match.NameType == "Primary Name");
    nameMatches = nameMatches.Except(allCatNameMatches.Where(c => c != primaryMatch)); 
}

现在这段代码,当我第一次运行它时,只是挂起了。我觉得很奇怪。我穿过它,它似乎有效,但经过 10 次迭代(总共上限为 100 只猫),它开始减速,然后最终它是冰川,然后完全挂起来。

我想也许它错误地做了一些密集的数据库工作,但分析器显示除了检索猫名的初始列表之外,没有执行任何 SQL。

我决定将其从 nameMatch 的IEnumerable更改为 List,并将适当的.ToList()放在最后一行。在我这样做之后,它立即完美地工作。

我想问的问题是,为什么?

为什么当我将代码从 IEnumerable 更改为列表时,它有效

如果没有ToList(),您将在nameMatches中构建一个等待延迟执行的嵌套IEnumerable链。这可能还不错,除了您还在每次迭代中调用FirstOrDefault,这将执行链。因此,在迭代编号 n 上,您将执行循环 n-1 次中包含的过滤器操作。如果你有 1000 只不同的猫,Linq 链将被执行 1000 + 99 + ... + 1 次。(我认为你有一些东西是O(n³)

寓意是,如果你想使用延迟执行,请确保你只执行一次你的链。

让我们稍微简化一下您的代码:

foreach (int catId in catIds.Distinct())
{
    var allCatNameMatches = nameMatches.Where(c => c.Match.CatId == catId);
    var primaryMatch = null;
    nameMatches = nameMatches.Except(allCatNameMatches.Where(c => c != primaryMatch)); 
}

还有更多:

foreach (int catId in catIds.Distinct())
{
    nameMatches = nameMatches.Where(c => c.Match.CatId == catId);
    var primaryMatch = null;
    nameMatches = nameMatches.Except(nameMatches.Where(c => c != primaryMatch)); 
}

在后者中,很明显,由于延迟执行foreach身体的每一次传递都会延长WhereExcept链。然后记住var primaryMatch = allCatNameMatches.FirstOrDefault.它不会延迟执行,因此在foreach的每次迭代中,它应该执行所有链。因此它挂起了。