47

我有我看起来像这样的代码:

List<Type> Os;

...

foreach (Type o in Os)
    if (o.cond)
        return;  // Quitting early is important for my case!
    else
        Os.Remove(o);

... // Other code

这不起作用,因为当您在该列表的foreach循环内时,您无法从列表中删除:

有没有解决问题的通用方法?

如果需要,我可以切换到其他类型。

选项 2:

List<Type> Os;

...

while (Os.Count != 0)
     if (Os[0].cond)
         return;
     else
         Os.RemoveAt(0);

... // Other code

丑陋,但它应该工作。

4

17 回答 17

59

您可以向后遍历列表:

for (int i = myList.Count - 1; i >= 0; i--)
{
    if (whatever) myList.RemoveAt(i);
}

为了回应您关于在找到不删除的项目时想要退出的评论,那么仅使用 while 循环将是最好的解决方案。

于 2009-05-06T22:47:19.503 回答
55

在 foreach 循环中,您永远不应该从您正在迭代的集合中删除任何内容。这基本上就像锯你坐的树枝。

使用您的 while 替代方案。这是要走的路。

于 2009-05-06T22:48:24.757 回答
31

您真的需要在foreach循环中执行此操作吗?

这将获得与您的示例相同的结果,即从列表中删除所有项目,直到第一个符合条件的项目(或者如果它们都不符合条件,则删除所有项目)。

int index = Os.FindIndex(x => x.cond);

if (index > 0)
    Os.RemoveRange(0, index);
else if (index == -1)
    Os.Clear();
于 2009-05-06T23:22:48.140 回答
15

我是一名 Java 程序员,但这样的工作:

List<Type> Os;
List<Type> Temp;
...
foreach (Type o in Os)
    if (o.cond)
        Temp.add(o);
Os.removeAll(Temp);  
于 2009-05-06T23:29:04.613 回答
13

我的分析库刚刚遇到这个问题。我试过这个:

for (int i = 0; i < list.Count; i++)
{                
   if (/*condition*/)
   {
       list.RemoveAt(i);
       i--;
   }
}

这很简单,但我没有想到任何突破点。

于 2009-05-06T23:06:50.377 回答
12

这是最简单的解决方案为什么

问题:

通常,我们从原始列表中删除,这会产生维护列表计数和迭代器位置的问题。

List<Type> Os = ....;
Os.ForEach(
    delegate(Type o) {
        if(!o.cond) Os.Remove(o);
    }
);

解决方案 - LINQ.ForEach

请注意,我添加的只是ToList(). 这将创建一个您执行 ForEach 的新列表,因此您可以删除原始列表,但继续遍历整个列表。

List<Type> Os = ....;
Os.ToList().ForEach(
    delegate(Type o) {
        if(!o.cond) Os.Remove(o);
    }
);

解决方案 - 常规foreach

这种技术也适用于常规foreach语句。

List<Type> Os = ....;
foreach(Type o in Os.ToList()) {
  if(!o.cond) Os.Remove(o);
}

struct请注意,如果您的原始列表包含元素,则此解决方案将不起作用。

于 2012-02-21T17:08:09.027 回答
11

我知道你要求别的东西,但如果你想有条件地删除一堆元素,你可以使用 lambda 表达式:

Os.RemoveAll(o => !o.cond);
于 2010-08-30T17:56:22.147 回答
9
 Os.RemoveAll(delegate(int x) { return /// });
于 2009-05-06T22:47:52.793 回答
4

我会尝试找到不满足谓词的第一个项目的索引并对其执行 RemoveRange(0, index) 。如果没有别的,应该有更少的 Remove 调用。

于 2009-05-06T23:01:59.577 回答
3

更新:为完整性添加

正如一些人回答的那样,您不应该在使用 GetEnumerator() 迭代集合时修改集合(示例foreach)。该框架通过引发异常来阻止您执行此操作。对此的通用解决方案是“手动”迭代for(参见其他答案)。小心你的索引,这样你就不会跳过项目或重新评估相同的项目两次(通过使用i--或向后迭代)。

但是,对于您的具体情况,我们可以优化删除操作......下面的原始答案。


如果您想要删除所有项目,直到满足给定条件(这就是您的代码所做的),您可以这样做:

bool exitCondition;

while(list.Count > 0 && !(exitCondition = list[0].Condition))
   list.RemoveAt(0);

或者,如果您想使用单个删除操作:

SomeType exitCondition;
int index = list.FindIndex(i => i.Condition);

if(index < 0)
    list.Clear();
else
{
    exitCondition = list[0].State;
    list.RemoveRange(0, count);
}

注意:因为我假设item.Conditionbool,所以我item.State用来保存退出条件。

更新:为两个示例添加了边界检查和保存退出条件

于 2009-05-06T23:17:30.070 回答
2

你可以用 linq 做到这一点

MyList = MyList.Where(x=>(someCondition(x)==true)).ToList()
于 2013-03-12T14:14:04.330 回答
1

在Removing items in a list while iterateing through it 中有很好的讨论。

他们提议:

for(int i = 0; i < count; i++)
{
    int elementToRemove = list.Find(<Predicate to find the element>);

    list.Remove(elementToRemove);
}
于 2009-05-06T22:46:29.660 回答
1

如果您知道您的列表不是很大,您可以使用

foreach (Type o in new List<Type>(Os))
    ....

这将创建列表的临时副本。然后,您的 remove() 调用将不会干扰迭代器。

于 2009-05-06T22:53:48.100 回答
1

看着Enumerable.SkipWhile()

Enumerable.SkipWhile( x => condition).ToList()

通常不改变列表,使生活更容易。:)

于 2009-06-06T13:49:13.947 回答
1

Anzurio 的解决方案可能是最直接的,但如果您不介意将一堆接口/类添加到实用程序库中,这里还有一个干净的解决方案。

你可以这样写

List<Type> Os;
...
var en = Os.GetRemovableEnumerator();
while (en.MoveNext())
{
    if (en.Current.Cond)
        en.Remove();
}

将以下受JavaIterator<T>.remove启发的基础设施放入您的实用程序库中:

static class Extensions
{
    public static IRemovableEnumerator<T> GetRemovableEnumerator<T>(this IList<T> l)
    {
        return new ListRemovableEnumerator<T>(l);
    }
}

interface IRemovableEnumerator<T> : IEnumerator<T>
{
    void Remove();
}

class ListRemovableEnumerator<T> : IRemovableEnumerator<T>
{
    private readonly IList<T> _list;
    private int _count;
    private int _index;
    public ListRemovableEnumerator(IList<T> list)
    {
        _list = list;
        _count = list.Count;
        _index = -1;
    }

    private void ThrowOnModification()
    {
        if (_list.Count != _count)
            throw new InvalidOperationException("List was modified after creation of enumerator");
    }
    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        ThrowOnModification();
        if (_index + 1 == _count)
            return false;
        _index++;
        return true;
    }

    public void Reset()
    {
        ThrowOnModification();
        _index = -1;
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public T Current
    {
        get { return _list[_index]; }
    }

    public void Remove()
    {
        ThrowOnModification();
        _list.RemoveAt(_index);
        _index--;
        _count--;
    }
}
于 2013-11-29T02:11:08.167 回答
0

我刚刚遇到了同样的问题并通过使用以下方法解决了它:

foreach (Type o in (new List(Os))) { if (something) Os.Remove(o); }

它遍历列表的副本并从原始列表中删除。

于 2014-06-05T07:58:43.443 回答
0

在列表中添加要删除的项目,然后使用以下命令删除这些项目RemoveAll

List<Type> Os;
List<Type> OsToRemove=new List<Type>();
...
foreach (Type o in Os){
    if (o.cond)
        return;
    else
        OsToRemove.Add(o);
}
Os.RemoveAll(o => OsToRemove.Contains(o));
于 2017-05-24T09:41:48.323 回答