迭代列表并从中删除项目的最佳方法是什么?

时间:2016-04-18 09:28:00

标签: c# list iteration

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

5 个答案:

答案 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)

两种方法均未显示出显着差异。 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

     

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

答案 4 :(得分:1)

另一种方法可以是预先列出该列表,将项目设置为null,然后执行linq remove to(item =&gt; null),如前所述,由您自己选择适合您的方式