C# 在迭代列表时从列表中删除/添加项目

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17233558/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 08:54:28  来源:igfitidea点击:

Remove/Add items to/from a list while iterating it

c#.netextension-methods

提问by Atrotygma

First, I know this isn't possible out of the box because of obvious reasons.

首先,我知道由于显而易见的原因,这不可能开箱即用。

foreach(string item in myListOfStrings) {
    myListOfStrings.Remove(item);
}

The snipped above is one of the most horrible things I've ever seen. So, how do you achieve it then? You could iterate through the list backwards using for, but I don't like this solution either.

上面的剪辑是我见过的最可怕的事情之一。那么,你如何实现它呢?您可以使用 向后遍历列表for,但我也不喜欢这个解决方案。

What I'm wondering is: Is there a method/extensions that returns an IEnumerable from the current list, something like a floating copy? LINQ has numerous extension methods that do exactly this, but you always have to do something with it, such as filtering (where, take...).

我想知道的是:是否有一种方法/扩展可以从当前列表中返回一个 IEnumerable,类似于浮动副本?LINQ 有许多扩展方法可以做到这一点,但你总是需要用它做一些事情,比如过滤(where, take...)。

I'm looking forward to something like this:

我很期待这样的事情:

foreach(string item in myListOfStrings.Shadow()) {
   myListOfStrings.Remove(item);
}

where as .Shadow() is:

其中 .Shadow() 是:

public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) {
    return new IEnumerable<T>(source);
    // or return source.Copy()
    // or return source.TakeAll();
}

Example

例子

foreach(ResponseFlags flag in responseFlagsList.Shadow()) {
    switch(flag) {
        case ResponseFlags.Case1:
            ...
        case ResponseFlags.Case2:
            ...
    }
    ...
    this.InvokeSomeVoidEvent(flag)
    responseFlagsList.Remove(flag);
}


Solution

解决方案

This is how I solved it, and it works like a charm:

这就是我解决它的方法,它就像一个魅力:

public static IEnumerable<T> Shadow<T>(this IEnumerable<T> source) where T: new() {
    foreach(T item in source)
        yield return item;
}

It's not that super fast (obviously), but it's safe and exactly what I intended to do.

它不是那么快(显然),但它是安全的,正是我打算做的。

采纳答案by svinja

Removing multiple elements from a list 1 by 1 is a C# anti-pattern due to how lists are implemented.

由于列表的实现方式,从列表中 1 个删除多个元素是 C# 反模式。

Of course, it can be done with a for loop (instead of foreach). Or it can be done by making a copy of the list. But here is why it should not be done. On a list of 100000 random integers, this takes 2500 ms on my machine:

当然,它可以用 for 循环(而不是 foreach)来完成。或者可以通过制作列表的副本来完成。但这就是为什么不应该这样做的原因。在 100000 个随机整数的列表中,这在我的机器上需要 2500 毫秒:

       foreach (var x in listA.ToList())
            if (x % 2 == 0)
                listA.Remove(x);

and this takes 1250 ms:

这需要 1250 毫秒:

        for (int i = 0; i < listA.Count; i++)
            if (listA[i] % 2 == 0)
                listA.RemoveAt(i--);

while these two take 5 and 2 ms respectively:

而这两个分别需要 5 和 2 毫秒:

        listB = listB.Where(x => x % 2 != 0).ToList();

        listB.RemoveAll(x => x % 2 == 0);

This is because when you remove an element from a list, you are actually deleting from an array, and this is O(N) time, as you need to shift each element afterthe deleted element one position to the left. On average, this will be N/2 elements.

这是因为当您从列表中删除一个元素时,您实际上是从一个数组中删除,这是 O(N) 时间,因为您需要将删除的元素的每个元素向左移动一个位置。平均而言,这将是 N/2 个元素。

Remove(element) also needs to find the element before removing it. So Remove(element) will actually always take N steps - elementindexsteps to find the element, N - elementindexsteps to remove it - in total, N steps.

Remove(element) 也需要在删除之前找到该元素。所以 Remove(element) 实际上总是需要 N 步 -elementindex找到元素的N - elementindex步骤,删除它的步骤 - 总共 N 步。

RemoveAt(index) doesn't have to find the element, but it still has to shift the underlying array, so on average, a RemoveAt is N/2 steps.

RemoveAt(index) 不必找到元素,但它仍然需要移动底层数组,因此平均而言,RemoveAt 是 N/2 步。

The end result is O(N^2) complexity either way, as you're removing up to N elements.

无论哪种方式,最终结果都是 O(N^2) 复杂度,因为您要删除最多 N 个元素。

Instead, you should use Linq, which will modify the entire list in O(N) time, or roll your own, but you should not use Remove (or RemoveAt) in a loop.

相反,您应该使用 Linq,它会在 O(N) 时间内修改整个列表,或者滚动您自己的列表,但您不应在循环中使用 Remove(或 RemoveAt)。

回答by DGibbs

Why not just do:

为什么不这样做:

foreach(string item in myListOfStrings.ToList()) 
{
    myListOfStrings.Remove(item);
}

To create a copy of the original and use for iterating, then remove from the existing.

创建原始副本并用于迭代,然后从现有中删除。

If you really need your extension method you could perhaps create something more readable to the user such as:

如果你真的需要你的扩展方法,你也许可以创建一些对用户更具可读性的东西,例如:

 public static IEnumerable<T> Shadow<T>(this IEnumerable<T> items)
 {
     if (items == null)
        throw new NullReferenceException("Items cannot be null");

     List<T> list = new List<T>();
     foreach (var item in items)
     {
         list.Add(item);
     }
     return list;
 }

Which is essentially the same as .ToList().

这与.ToList().

Calling:

调用:

foreach(string item in myListOfStrings.Shadow())

foreach(string item in myListOfStrings.Shadow())

回答by dasblinkenlight

You do not LINQ extension methods for this - you can create a new list explicitly, like this:

您没有为此使用 LINQ 扩展方法 - 您可以显式创建一个新列表,如下所示:

foreach(string item in new List<string>(myListOfStrings)) {
    myListOfStrings.Remove(item);
}

回答by Azhar Khorasany

You have to create a copy of the original list while iterating as below:

您必须在迭代时创建原始列表的副本,如下所示:

        var myListOfStrings = new List<string>();

        myListOfStrings.Add("1");
        myListOfStrings.Add("2");
        myListOfStrings.Add("3");
        myListOfStrings.Add("4");
        myListOfStrings.Add("5");

        foreach (string item in myListOfStrings.ToList())
        {
            myListOfStrings.Remove(item);
        }

回答by Matthew Watson

Your example removes all items from the string, so it's equivalent to:

您的示例从字符串中删除所有项目,因此它等效于:

myListOfStrings.Clear();

It is also equivalent to:

它也相当于:

myListOfStrings.RemoveAll(x => true); // Empties myListOfStrings

But what I think you're looking for is a way to remove items for which a predicate is true - which is what RemoveAll()does.

但是我认为您正在寻找的是一种删除谓词为真的项目的方法 - 这就是这样RemoveAll()做的。

So you could write, for example:

所以你可以写,例如:

myListOfStrings.RemoveAll(x => x == "TEST"); // Modifies myListOfStrings

Or use any other predicate.

或者使用任何其他谓词。

However, that changes the ORIGINAL list; If you just want a copy of the list with certain items removed, you can just use normal Linq:

然而,这改变了原始列表;如果您只想要删除某些项目的列表副本,您可以使用普通的 Linq:

// Note != instead of == as used in Removeall(), 
// because the logic here is reversed.

var filteredList = myListOfStrings.Where(x => x != "TEST").ToList(); 

回答by Ricardo Rodrigues

Picking up on the answer of svinjaI do believe the most efficient way of solving this problem is by doing:

接受svinja的答案,我相信解决这个问题的最有效方法是:

for (int i = 0; i < listA.Count;) {
    if (listA[i] % 2 == 0)
        listA.RemoveAt(i);
    else
        i++;
}

It improves on the answer by removing unnecessary sums and subtractions.

它通过删除不必要的总和和减法来改进答案。