C# 迭代列表并从中删除项目的最佳方法?

C# 迭代列表并从中删除项目的最佳方法?,c#,list,iteration,C#,List,Iteration,我需要遍历列表,并删除满足特定条件的项 我看到了这个答案(): 使用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;

我需要遍历
列表
,并删除满足特定条件的项

我看到了这个答案():

使用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));
一种方法比另一种好吗

编辑:


我不想使用
RemoveAll(…)
,因为在条件之前,循环中有大量代码。

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

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);
}
var list=新列表(可枚举范围(1,10));
对于(int i=list.Count-1;i>=0;i--)
{
如果(列表[i]>5)
列表。删除(i);
}

问题在于,
foreach
无法在修改集合时使用集合,这将导致
无效操作异常

foreach
内部使用
.ToList()
可以避免这种情况,但这样会复制整个集合,从而导致在集合中循环两次(第一次是复制所有元素,第二次是过滤)

for
循环是更好的选择,因为我们可以遍历一次集合。除此之外,您还可以使用
LINQ
Where()
方法筛选集合,这些方法包括:

var myFilteredCollection = myList.Where(i => i <= 5).ToList();
并在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);
}
var newList=新列表(myList.Count);
foreach(myList中的var项)
{
//如果某些条件不适用:
新建列表。添加(项);
}
附言

但是正如前面提到的那样,
.RemoveAll()
更好,因为这样你就不必“重新发明轮子”

你可以随意地在列表上循环,而
for
循环是最有效的循环:

  for (int i = safePendingList.Count - 1; i >= 0; --i) 
    if (condition)
      safePendingList.RemoveAt(i);
如果要在范围内(而不是在整个列表中)删除,只需修改for loop:

for (int i = safePendingList.Count - 1; i >= 0; i--)
{
    // some code
    // safePendingList.RemoveAt(i);
}
或者,如果必须删除正向循环中的项目:

然而,在许多情况下,最合理的计划就是让.Net为您工作:


两种方法均未显示显著差异
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);
}
你只需要扭转这种状况



我不是建议使用这种方法,只是证明
foreach
不一定比
慢,因为

另一种方法可以是foreach列表,将项设置为null,然后执行linq remove to(item=>null),如前所述,这取决于您选择更适合您的方式

“更好”如在什么?演出记忆?易读性?
效率不低于
foreach
。您可以使用
RemoveAll
,而不是循环,它具有更好的性能:您不能从foreach中使用的同一列表中删除项,您将得到一个异常(System.InvalidoOperationException)。LINQ中没有
RemoveAll()
这样的东西。@poke这对于数组肯定不是真的,对于其中的
foreach
进行了高度优化。
myList.Where(x=>x>5)
?@arrowd-要获得相同的结果,它必须是
myList=myList.Where(x=>x我不想使用LINQ,因为条件很复杂,并且是作为循环构建的progresses@arrowd创建列表的新副本时省略某些项目与从现有列表中删除项目不同。@arrowd他说的是使用
RemoveAll()
,而不是
RemoveAt())
没错,如果你想知道哪匹马跑得更快……通常最好的策略是让.Net来完成它的工作-
删除所有的
+1@DmitryBychenko我从理论上期待这个结果,但是是的,没有什么比赛马更好:-)我相信你已经知道了,但是因为“…但我知道,
for
的效率低于
foreach
。。。“在这个问题上,确保相反情况成立的最佳方法是让马儿运行。10个元素的列表对于这种测试来说太小了,因为内存分配等的开销会扭曲结果。尝试使用100万个元素的列表(删除索引小于500000的所有项目)
MethodA
MethodB
之间的性能差异很可能是由于使用了
Remove
而不是
RemoveAt
Remove
必须在整个列表中搜索有问题的项目,而使用
RemoveAt
,您会告诉它确切的位置。需要记住的一点是:使用
RemoveAt();
是最接近于高阶
过滤器的
函数,而且由于其简短易读,
RemoveAll
的主要优点是,无论需要删除多少项或哪些项,它都需要线性时间。基于
RemoveAt
的解决方案可能需要二次时间,因为需要移动大部分内容重复列出。你把我的答案塞进了你的:-\
  // 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() - CPU and Memory overhead (copying)
  foreach (var item in safePendingList.ToList()) {
    if (condition)
      myList.Remove(item); // Overhead: searching
  }
  safePendingList.RemoveAll(item => condition);
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);
}
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;
}