循环访问列表并从中删除项目的最佳方式

本文关键字:删除项目 最佳 方式 访问 列表 循环 | 更新日期: 2024-09-25 11:02:57

我需要遍历一个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(...) ,因为在条件之前,循环内有大量代码。

循环访问列表并从中删除项目的最佳方式

无论如何,你必须遍列表,而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);

删除具有特定条件的项目,您可以使用

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);
}

这两种方法都没有显示出显着差异。 forRemoveAll看起来更快一些(更有可能是因为使用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

C: 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 (应该与forRemoveAll方法一样快(在我的分析中它有点慢 - 因为它分配了一个新列表,我认为 - 但几乎可以忽略不计(:

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

问题是foreach在修改时无法使用集合,这将导致InvalidOperationException

foreach 中使用 .ToList() 可以避免这种情况,但这会复制整个集合,导致循环访问集合两次(第一次复制所有元素时,第二次过滤它(。

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);
}

附言

但如前所述.RemoveAll()更好,因为这样您就不必"重新发明轮子">

另一种方法是对列表进行搜索,将项目设置为 null,然后执行 linq 删除到(项目 => null(,如前所述,由您选择更适合您的方式