如何在迭代时从通用列表中删除元素?

时间:2009-10-17 14:12:35

标签: c# list loops generics key-value

我正在寻找更好的 模式 ,以便处理每个需要处理的元素列表,然后根据结果从列表中删除。

您无法在.Remove(element)内使用foreach (var element in X)(因为它会导致Collection was modified; enumeration operation may not execute.例外)...您也无法使用for (int i = 0; i < elements.Count(); i++)和{{1因为它会扰乱你在集合中相对于.RemoveAt(i)的当前位置。

有优雅的方法吗?

28 个答案:

答案 0 :(得分:639)

使用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 method与谓词一起使用来进行测试:

safePendingList.RemoveAll(item => item.Value == someValue);

以下是一个简化示例:

var list = new List<int>(Enumerable.Range(1, 10));
Console.WriteLine("Before:");
list.ForEach(i => Console.WriteLine(i));
list.RemoveAll(i => i > 5);
Console.WriteLine("After:");
list.ForEach(i => Console.WriteLine(i));

答案 1 :(得分:82)

简单明了的解决方案:

在您的收藏集上使用标准的for循环向后,并RemoveAt(i)删除元素。

答案 2 :(得分:63)

当您想要在迭代迭代时从集合中删除元素时,首先应该想到反向迭代。

幸运的是,有一个比写一个for循环更优雅的解决方案,它涉及不必要的打字并且容易出错。

ICollection<int> test = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

foreach (int myInt in test.Reverse<int>())
{
    if (myInt % 2 == 0)
    {
        test.Remove(myInt);
    }
}

答案 3 :(得分:51)

 foreach (var item in list.ToList()) {
     list.Remove(item);
 }

如果您将“.ToList()”添加到您的列表(或LINQ查询的结果),您可以直接从“列表”中删除“item”而不用可怕的“集合已修改;枚举操作可能无法执行。“错误。编译器会复制“list”,以便您可以安全地对阵列执行删除操作。

虽然此模式不是超级高效,但它具有自然的感觉,并且足够灵活,几乎适用于任何情况。例如,当您想要将每个“项目”保存到数据库并仅在数据库保存成功时将其从列表中删除。

答案 4 :(得分:20)

在通用列表上使用ToArray()允许您在通用列表上执行删除(项目):

        List<String> strings = new List<string>() { "a", "b", "c", "d" };
        foreach (string s in strings.ToArray())
        {
            if (s == "b")
                strings.Remove(s);
        }

答案 5 :(得分:20)

选择执行所需的元素,而不是尝试删除所需的元素。这比删除元素更容易(并且通常也更有效)。

var newSequence = (from el in list
                   where el.Something || el.AnotherThing < 0
                   select el);

我想发布这篇评论作为评论,以回应迈克尔·狄龙在下面留下的评论,但它太长了,无论如何都可能在我的答案中有用:

就个人而言,我永远不会逐个删除项目,如果你确实需要删除,那么调用带有谓词的RemoveAll并仅重新排列内部数组一次,而Remove执行您删除的每个元素的Array.Copy操作。 RemoveAll效率更高。

当您向后迭代列表时,您已经拥有了要删除的元素的索引,因此调用RemoveAt会更有效率,因为Remove首先执行此操作遍历列表以查找您要删除的元素的索引,但您已经知道该索引。

总而言之,我认为没有理由在for循环中调用Remove。理想情况下,如果可能的话,使用上面的代码根据需要从列表中流式传输元素,因此根本不需要创建第二个数据结构。

答案 6 :(得分:18)

使用.ToList()将复制您的列表,如此问题中所述: ToList()-- Does it Create a New List?

通过使用ToList(),您可以从原始列表中删除,因为您实际上正在迭代副本。

foreach (var item in listTracked.ToList()) {    

        if (DetermineIfRequiresRemoval(item)) {
            listTracked.Remove(item)
        }

     }

答案 7 :(得分:12)

如果确定要删除哪些项目的函数没有副作用且不改变项目(它是纯函数),那么简单而有效(线性时间)的解决方案是:

list.RemoveAll(condition);

如果有副作用,我会使用类似的东西:

var toRemove = new HashSet<T>();
foreach(var item in items)
{
     ...
     if(condition)
          toRemove.Add(item);
}
items.RemoveAll(toRemove.Contains);

这仍然是线性时间,假设哈希是好的。但是由于hashset,它的内存使用量增加了。

最后,如果您的列表只是IList<T>而不是List<T>,我建议我回答How can I do this special foreach iterator?。与IList<T>的典型实现相比,这将具有线性运行时,与许多其他答案的二次运行时相比。

答案 8 :(得分:10)

如果在条件下进行任何删除,您可以使用

list.RemoveAll(item => item.Value == someValue);

答案 9 :(得分:9)

List<T> TheList = new List<T>();

TheList.FindAll(element => element.Satisfies(Condition)).ForEach(element => TheList.Remove(element));

答案 10 :(得分:6)

你不能使用foreach,但是当你删除一个项目时你可以向前迭代并管理你的循环索引变量,如下所示:

for (int i = 0; i < elements.Count; i++)
{
    if (<condition>)
    {
        // Decrement the loop counter to iterate this index again, since later elements will get moved down during the remove operation.
        elements.RemoveAt(i--);
    }
}

请注意,通常所有这些技术都依赖于迭代集合的行为。此处显示的技术将与标准List(T)一起使用。 (很有可能编写自己的集合类和迭代器, 允许在foreach循环中删除项目。)

答案 11 :(得分:4)

在迭代该列表时在列表中使用RemoveRemoveAt故意变得困难,因为它几乎总是做错了。你可能能够使用一些聪明的技巧,但它会非常慢。每次调用Remove时,都必须扫描整个列表以找到要删除的元素。每次调用RemoveAt时,都必须将后续元素向左移动1个位置。因此,使用RemoveRemoveAt的任何解决方案都需要二次时间, O(n²)

如果可以,请使用RemoveAll。否则,以下模式将以线性时间 O(n)过滤列就地

// Create a list to be filtered
IList<int> elements = new List<int>(new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
// Filter the list
int kept = 0;
for (int i = 0; i < elements.Count; i++) {
    // Test whether this is an element that we want to keep.
    if (elements[i] % 3 > 0) {
        // Add it to the list of kept elements.
        elements[kept] = elements[i];
        kept++;
    }
}
// Unfortunately IList has no Resize method. So instead we
// remove the last element of the list until: elements.Count == kept.
while (kept < elements.Count) elements.RemoveAt(elements.Count-1);

答案 12 :(得分:3)

希望 “模式”是这样的:

foreach( thing in thingpile )
{
    if( /* condition#1 */ )
    {
        foreach.markfordeleting( thing );
    }
    elseif( /* condition#2 */ )
    {
        foreach.markforkeeping( thing );
    }
} 
foreachcompleted
{
    // then the programmer's choices would be:

    // delete everything that was marked for deleting
    foreach.deletenow(thingpile); 

    // ...or... keep only things that were marked for keeping
    foreach.keepnow(thingpile);

    // ...or even... make a new list of the unmarked items
    others = foreach.unmarked(thingpile);   
}

这会使代码与程序员大脑中正在进行的过程保持一致。

答案 13 :(得分:3)

我会从LINQ查询中重新分配列表,该查询过滤掉了您不想保留的元素。

list = list.Where(item => ...).ToList();

除非列表非常大,否则在执行此操作时不会出现明显的性能问题。

答案 14 :(得分:2)

这里没有提到一个选项。

如果您不介意在项目中的某处添加一些代码,您可以添加和扩展 List 以返回一个类的实例,该实例会反向遍历列表。

你会像这样使用它:

foreach (var elem in list.AsReverse())
{
    //Do stuff with elem
    //list.Remove(elem); //Delete it if you want
}

这是扩展的样子:

public static class ReverseListExtension
{
    public static ReverseList<T> AsReverse<T>(this List<T> list) => new ReverseList<T>(list);

    public class ReverseList<T> : IEnumerable
    {
        List<T> list;
        public ReverseList(List<T> list){ this.list = list; }

        public IEnumerator GetEnumerator()
        {
            for (int i = list.Count - 1; i >= 0; i--)
                yield return list[i];
            yield break;
        }
    }
}

这基本上就是没有分配的list.Reverse()。

就像有些人提到的那样,您仍然会遇到逐个删除元素的缺点,如果您的列表非常长,这里的一些选项会更好。但我认为有一个世界,有人会想要 list.Reverse() 的简单性,而不需要内存开销。

答案 15 :(得分:2)

假设谓词是元素的布尔属性,如果为true,则应删除该元素:

        int i = 0;
        while (i < list.Count())
        {
            if (list[i].predicate == true)
            {
                list.RemoveAt(i);
                continue;
            }
            i++;
        }

答案 16 :(得分:2)

foreach(var item in list.ToList())

{

if(item.Delete) list.Remove(item);

}

只需从第一个列表创建一个全新的列表。我说“简单”而不是“正确”,因为创建一个全新的列表可能比以前的方法具有更高的性能(我没有打扰任何基准测试。)我通常更喜欢这种模式,它也可以用来克服Linq-To-Entities限制。

for(i = list.Count()-1;i>=0;i--)

{

item=list[i];

if (item.Delete) list.Remove(item);

}

这种方式使用普通的For循环向后循环列表。如果集合的大小发生变化,那么向前执行此操作可能会有问题,但向后应始终是安全的。

答案 17 :(得分:2)

在迭代列表中删除项目的最佳方法是使用 RemoveAll() 。但人们写的主要问题是他们必须在循环内做一些复杂的事情和/或有复杂的比较案例。

解决方案仍然是使用 RemoveAll() ,但请使用此表示法:

var list = new List<int>(Enumerable.Range(1, 10));
list.RemoveAll(item => 
{
    // Do some complex operations here
    // Or even some operations on the items
    SomeFunction(item);
    // In the end return true if the item is to be removed. False otherwise
    return item > 5;
});

答案 18 :(得分:1)

我会这样

using System.IO;
using System;
using System.Collections.Generic;

class Author
    {
        public string Firstname;
        public string Lastname;
        public int no;
    }

class Program
{
    private static bool isEven(int i) 
    { 
        return ((i % 2) == 0); 
    } 

    static void Main()
    {    
        var authorsList = new List<Author>()
        {
            new Author{ Firstname = "Bob", Lastname = "Smith", no = 2 },
            new Author{ Firstname = "Fred", Lastname = "Jones", no = 3 },
            new Author{ Firstname = "Brian", Lastname = "Brains", no = 4 },
            new Author{ Firstname = "Billy", Lastname = "TheKid", no = 1 }
        };

        authorsList.RemoveAll(item => isEven(item.no));

        foreach(var auth in authorsList)
        {
            Console.WriteLine(auth.Firstname + " " + auth.Lastname);
        }
    }
}

输出

Fred Jones
Billy TheKid

答案 19 :(得分:0)

我的方法是首先创建一个索引列表,这些列表应该被删除。然后我循环索引并从初始列表中删除项目。这看起来像这样:

var messageList = ...;
// Restrict your list to certain criteria
var customMessageList = messageList.FindAll(m => m.UserId == someId);

if (customMessageList != null && customMessageList.Count > 0)
{
    // Create list with positions in origin list
    List<int> positionList = new List<int>();
    foreach (var message in customMessageList)
    {
        var position = messageList.FindIndex(m => m.MessageId == message.MessageId);
        if (position != -1)
            positionList.Add(position);
    }
    // To be able to remove the items in the origin list, we do it backwards
    // so that the order of indices stays the same
    positionList = positionList.OrderByDescending(p => p).ToList();
    foreach (var position in positionList)
    {
        messageList.RemoveAt(position);
    }
}

答案 20 :(得分:0)

复制您正在迭代的列表。然后从副本中删除并与原始内容进行交互。倒退是令人困惑的,并行循环时效果不佳。

var ids = new List<int> { 1, 2, 3, 4 };
var iterableIds = ids.ToList();

Parallel.ForEach(iterableIds, id =>
{
    ids.Remove(id);
});

答案 21 :(得分:0)

从列表中删除项目的成本与要删除项目之后的项目数量成比例。在前半部分项目有资格移除的情况下,任何基于单独移除项目的方法最终都必须执行N * N / 4项目复制操作,如果列表很大,这可能会非常昂贵

更快的方法是扫描列表以找到要删除的第一个项目(如果有),然后从该点向前复制应保留的每个项目到它所属的位置。完成此操作后,如果应保留R项,则列表中的前R项将是那些R项,并且所有需要删除的项将在最后。如果以相反的顺序删除这些项目,系统将不会最终复制其中的任何项目,因此如果列表中有N个项目,其中包括所有第一个F项目,则保留, 有必要复制R-F项目,并将列表缩小一个项目N-R次。所有线性时间。

答案 22 :(得分:0)

在C#中,一种简单的方法是标记要删除的对象,然后创建一个新列表以进行迭代...

Company.find()
    .or([{ 'name': { $regex: re }}, { 'url': { $regex: re }}])
    .exec(function (err, company) {
        if (err) return next(err);
        res.send(company);
    })

或更简单地使用linq。...

foreach(var item in list.ToList()){if(item.Delete) list.Remove(item);}  

但是值得考虑的是,在您忙于删除其他任务或线程的同时是否可以访问同一列表,并且可以使用ConcurrentList代替。

答案 23 :(得分:0)

使用属性跟踪要删除的元素,并在处理后将其全部删除。

using System.Linq;

List<MyProperty> _Group = new List<MyProperty>();
// ... add elements

bool cond = true;
foreach (MyProperty currObj in _Group)
{
    if (cond) 
    {
        // SET - element can be deleted
        currObj.REMOVE_ME = true;
    }
}
// RESET
_Group.RemoveAll(r => r.REMOVE_ME);

答案 24 :(得分:0)

只是想在此基础上加2美分,以防对任何人有帮助,我遇到了类似的问题,但是需要在数组列表进行迭代时从数组列表中删除多个元素。在我遇到错误并意识到在某些情况下索引大于数组列表的大小时(因为已删除了多个元素,但循环的索引没有保留),最高的最高答案一直为我做到这一点跟踪。我通过简单的检查来解决了这个问题:

ArrayList place_holder = new ArrayList();
place_holder.Add("1");
place_holder.Add("2");
place_holder.Add("3");
place_holder.Add("4");

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

// some method that removes multiple elements here
}

答案 25 :(得分:0)

For循环对此是不好的构造。

使用while

var numbers = new List<int>(Enumerable.Range(1, 3));

while (numbers.Count > 0)
{
    numbers.RemoveAt(0);
}

但是,如果您绝对必须使用for

var numbers = new List<int>(Enumerable.Range(1, 3));

for (; numbers.Count > 0;)
{
    numbers.RemoveAt(0);
}

或者,这个:

public static class Extensions
{

    public static IList<T> Remove<T>(
        this IList<T> numbers,
        Func<T, bool> predicate)
    {
        numbers.ForEachBackwards(predicate, (n, index) => numbers.RemoveAt(index));
        return numbers;
    }

    public static void ForEachBackwards<T>(
        this IList<T> numbers,
        Func<T, bool> predicate,
        Action<T, int> action)
    {
        for (var i = numbers.Count - 1; i >= 0; i--)
        {
            if (predicate(numbers[i]))
            {
                action(numbers[i], i);
            }
        }
    }
}

用法:

var numbers = new List<int>(Enumerable.Range(1, 10)).Remove((n) => n > 5);

答案 26 :(得分:0)

我发现自己处于类似的情况,我必须删除给定List<T>中的每个 th 元素。

for (int i = 0, j = 0, n = 3; i < list.Count; i++)
{
    if ((j + 1) % n == 0) //Check current iteration is at the nth interval
    {
        list.RemoveAt(i);
        j++; //This extra addition is necessary. Without it j will wrap
             //down to zero, which will throw off our index.
    }
    j++; //This will always advance the j counter
}

答案 27 :(得分:-4)

myList.RemoveAt(i--);

simples;