迭代IEnumerable时正在从集合中删除项

本文关键字:集合 删除 IEnumerable 迭代 | 更新日期: 2023-09-27 17:58:58

我有以下代码:

foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks
                                                  || bar.Key > toTicks))
{
   dataFromDataFeed.Remove(bar.Key);
}

这安全吗?还是我需要先将foreach中的IEnumerable转换为Dictionary<T,U>

谢谢。

迭代IEnumerable时正在从集合中删除项

不,这将轰炸或得到一个仍然有坏元素的结果。只需反转Where表达式:

var filtered = dataFromDataFeed.Where(bar => bar.Key >= fromTicks && bar.Key <= toTicks);
dataFromFeed = filtered.ToList();   // optional

目前还不清楚您是否真的需要更新列表,通常没有必要,因为您有一个非常好的枚举器,所以最后一条语句是// optional

请记住,像在原始代码中那样使用Remove()具有O(n*m)复杂性,非常糟糕。使用ToList()只需要O(m),但需要O(m)存储。用速度换取内存是程序员的一个常见决定,但这是一个slamdunk,除非m是巨大的(数亿,而你正在与OOM作斗争)或非常小。给定表达式,两者都不应适用。

在枚举集合时修改集合被认为是一种糟糕的做法,在许多情况下,这会导致抛出InvalidOperationException

您应该将值复制到另一个List或数组中(例如,在Where()调用之后调用ToList()),这样就不会修改原始数据。

foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks || bar.Key > toTicks).ToList())
{
    dataFromDataFeed.Remove(bar.Key);
}

它将抛出一个错误,因为您正在迭代集合。在枚举集合时不支持修改集合。

使用ToList()创建新列表

foreach (var bar in dataFromDataFeed.Where(bar => bar.Key < fromTicks
                                         || bar.Key > toTicks).ToList())
{
   dataFromDataFeed.Remove(bar.Key);
}

尽管Dictionary几乎可以肯定地包括一个方法,该方法将在每个项上所有谓词函数,并删除该谓词返回true的所有项,但它没有。因此,如果一个Dictionary包括一些与谓词匹配的项和另一些不匹配的项,那么获得一个只包括不满足谓词的项的字典的唯一方法是建立一个满足谓词的所有项的列表,然后从字典中删除列表上的所有项,或者构建一个只包含不满足谓词的项的字典,并放弃原来的项而使用新的项。哪种方法更好取决于要保留和丢弃的物品的相对数量。

作为替代方案,可以切换到使用ConcurrentDictionary。与Dictionary不同,ConcurrentDictionary将允许移除项目,而不会使正在进行的任何枚举无效。如果只在枚举项目时删除它们,我希望ConcurrentDictionary能够完全按照预期进行枚举。如果枚举一个项有时会导致代码删除另一个项,那么代码必须准备好这样一个事实,即删除尚未枚举的项可能会导致从枚举中省略该项,但不需要

尽管Dictionary通常比ConcurrentDictionary快,但如果"删除其中的项目…"操作很常见,并且必须删除或复制集合中相当一部分项目,则使用后者可能是值得的。