我需要迭代List<myObject>
并删除符合特定条件的项目。
我看到了这个答案(https://stackoverflow.com/a/1582317/5077434):
使用for循环反向迭代列表:
for (int i = safePendingList.Count - 1; i >= 0; i--) { // some code // safePendingList.RemoveAt(i); }
示例:
var list = new List<int>(Enumerable.Range(1, 10)); for (int i = list.Count - 1; i >= 0; i--) { if (list[i] > 5) list.RemoveAt(i); } list.ForEach(i => Console.WriteLine(i));
但我了解for
的效率低于foreach
,
所以我考虑使用后面的内容如下:
foreach (var item in myList.ToList())
{
// if certain condition applies:
myList.Remove(item)
}
一种方法比另一种更好吗?
修改
我不想使用RemoveAll(...)
,因为在条件之前循环中有大量代码。
答案 0 :(得分:21)
Willy-nilly你必须在列表上循环,for
循环是最有效的循环:
for (int i = safePendingList.Count - 1; i >= 0; --i)
if (condition)
safePendingList.RemoveAt(i);
如果要删除范围(不在整个列表中),只需修改 for 循环:
// No Enumarable.Range(1, 10) - put them into "for"
for (int i = Math.Min(11, safePendingList.Count - 1); i >= 1; --i)
if (condition)
safePendingList.RemoveAt(i);
或者如果您必须删除正向循环中的项目:
for (int i = 0; i < safePendingList.Count;) // notice ++i abscence
if (condition)
safePendingList.RemoveAt(i);
else
i += 1; // ++i should be here
相反safePendingList.ToList()
创建初始safePendingList
的副本,这意味着内存和 CPU 开销:
// safePendingList.ToList() - CPU and Memory overhead (copying)
foreach (var item in safePendingList.ToList()) {
if (condition)
myList.Remove(item); // Overhead: searching
}
但是,在许多情况下,大多数合理的计划只是为了让让.Net工作:
safePendingList.RemoveAll(item => condition);
答案 1 :(得分:7)
删除具有特定条件的项目,您可以使用
list.RemoveAll(item => item > 5);
而不是
var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] > 5)
list.RemoveAt(i);
}
答案 2 :(得分:4)
问题是foreach
在修改时无法与集合一起使用,这将导致InvalidOperationException
。
在.ToList()
内使用foreach
可以避免这种情况,但这会复制整个集合,导致循环收集两次(第一次复制所有元素,第二次复制过滤)。 / p>
for
循环是更好的选择,因为我们可以进行一次收集。除此之外,您可以使用LINQ
Where()
方法过滤收集,其中包括:
var myFilteredCollection = myList.Where(i => i <= 5).ToList();
如果条件很复杂,可以将此逻辑移到单独的方法:
private bool IsItemOk(myObject item)
{
... your complicated logic.
return true;
}
在LINQ查询中使用它:
var myFilteredCollection = myList.Where(i => IsItemOk(i)).ToList();
其他选择是反之亦然。创建新集合并根据条件添加元素。你可以使用foreach
循环:
var newList = new List<MyObject>(myList.Count);
foreach (var item in myList)
{
// if certain condition does not apply:
newList.Add(item);
}
P.S。
但正如已经提到的.RemoveAll()
更好,因为这样你就不必“重新发明轮子”
答案 3 :(得分:4)
两种方法均未显示出显着差异。 for
和RemoveAll
看起来更快一些(更可能是因为使用Remove
您正在复制列表并且需要重新创建它...使用值类型,这也意味着需要加倍所需的内存)。我做了一些分析:
public static void MethodA()
{
var list = new List<int>(Enumerable.Range(1, 10));
for (int i = list.Count - 1; i >= 0; i--)
{
if (list[i] > 5)
list.RemoveAt(i);
}
}
public static void MethodB()
{
var list = new List<int>(Enumerable.Range(1, 10));
foreach (var item in list.ToList())
{
if (item > 5)
list.Remove(item);
}
}
public static void MethodC()
{
var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(x => x > 5);
}
public static void Measure(string meas, Action act)
{
GC.Collect();
GC.WaitForPendingFinalizers();
var st = new Stopwatch();
st.Start();
for (var i = 0; i < 10000000; i++)
act();
st.Stop();
Console.WriteLine($"{meas}: {st.Elapsed}");
}
static void Main(string[] args)
{
Measure("A", MethodA);
Measure("B", MethodB);
Measure("C", MethodC);
}
结果:
00:00:04.1179163
B:00:00:07.7417853
00:00:04.3525255
其他运行提供了更多类似的结果(请注意,这是10个项目列表上的1000万次迭代[因此进行了1亿次操作] ...差异大约3秒并不是我称之为“重要”的,但你的里程可能会有所不同)
那么请选择您认为可以更好地阅读的内容(我个人使用RemoveAll
方法)
注意:随着您的列表增长(这只是10个项目),MethodB
(重复列表的foreach
)将会变慢,差异将会很大更高,因为列表的重复将更加昂贵。例如,执行1,000个项目的列表(条件为500而不是5)和100,000次迭代,使MethodB
为34秒,而其他两个项目在我的机器上运行不到2秒。所以是的,绝对不要使用MethodB
: - )
这并不意味着foreach
效率低于for
,只表示使用foreach
从列表中删除项目,需要复制所述列表,这是昂贵的。
另一种方法(使用foreach
)应该与for
或RemoveAll
方法一样快(在我的分析中它稍微慢一些 - 因为它分配了一个新列表,我想 - 但几乎可以忽略不计):
public static void MethodD()
{
var originalList = new List<int>(Enumerable.Range(1, 1000));
var list = new List<int>(originalList.Count);
foreach (var item in originalList)
{
if (item <= 500)
list.Add(item);
}
list.Capacity = list.Count;
}
你只需要扭转这种情况。
我不建议使用此方法,只是证明foreach
不一定比for
慢
答案 4 :(得分:1)
另一种方法可以是预先列出该列表,将项目设置为null,然后执行linq remove to(item =&gt; null),如前所述,由您自己选择适合您的方式