C# 在“foreach”循环中修改列表的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/759966/
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-05 00:09:44  来源:igfitidea点击:

What is the best way to modify a list in a 'foreach' loop?

c#.netlistenumerationenumerable

提问by Polo

A new feature in C# / .NET 4.0 is that you can change your enumerable in a foreachwithout getting the exception. See Paul Hymanson's blog entry An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumeratingfor information on this change.

C#/.NET 4.0 中的一个新功能是,您可以更改 a 中的可枚举项foreach而不会出现异常。有关此更改的信息,请参阅 Paul Hymanson 的博客文章An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumrating

What is the best way to do the following?

执行以下操作的最佳方法是什么?

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable)
    {
        item.Add(new item2)
    }
}

Usually I use an IListas a cache/buffer until the end of the foreach, but is there better way?

通常我使用 anIList作为缓存/缓冲区,直到 . 结束foreach,但有更好的方法吗?

采纳答案by Rik

The collection used in foreach is immutable. This is very much by design.

foreach 中使用的集合是不可变的。这在很大程度上是设计使然。

As it says on MSDN:

正如MSDN 上所说:

The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.

foreach 语句用于遍历集合以获取您想要的信息,但不能用于从源集合中添加或删除项目以避免不可预测的副作用。如果您需要从源集合中添加或删除项目,请使用 for 循环。

The post in the linkprovided by Poko indicates that this is allowed in the new concurrent collections.

Poko 提供的链接中的帖子表明这在新的并发集合中是允许的。

回答by Nippysaurus

You should really use for()instead of foreach()in this case.

在这种情况下,您应该真正使用for()而不是foreach()

回答by tvanfosson

Make a copy of the enumeration, using an IEnumerable extension method in this case, and enumerate over it. This would add a copy of every element in every inner enumerable to that enumeration.

在这种情况下使用 IEnumerable 扩展方法制作枚举的副本,并对其进行枚举。这会将每个内部可枚举中的每个元素的副本添加到该枚举中。

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}

回答by Anton Gogolev

Here's how you can do that (quick and dirty solution. If you reallyneed this kind of behavior, you should either reconsider your design or override all IList<T>members and aggregate the source list):

以下是您可以这样做的方法(快速而肮脏的解决方案。如果您确实需要这种行为,您应该重新考虑您的设计或覆盖所有IList<T>成员并汇总源列表):

using System;
using System.Collections.Generic;

namespace ConsoleApplication3
{
    public class ModifiableList<T> : List<T>
    {
        private readonly IList<T> pendingAdditions = new List<T>();
        private int activeEnumerators = 0;

        public ModifiableList(IEnumerable<T> collection) : base(collection)
        {
        }

        public ModifiableList()
        {
        }

        public new void Add(T t)
        {
            if(activeEnumerators == 0)
                base.Add(t);
            else
                pendingAdditions.Add(t);
        }

        public new IEnumerator<T> GetEnumerator()
        {
            ++activeEnumerators;

            foreach(T t in ((IList<T>)this))
                yield return t;

            --activeEnumerators;

            AddRange(pendingAdditions);
            pendingAdditions.Clear();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ModifiableList<int> ints = new ModifiableList<int>(new int[] { 2, 4, 6, 8 });

            foreach(int i in ints)
                ints.Add(i * 2);

            foreach(int i in ints)
                Console.WriteLine(i * 2);
        }
    }
}

回答by Josh G

You can't change the enumerable collection while it is being enumerated, so you will have to make your changes before or after enumerating.

您无法在枚举时更改可枚举集合,因此您必须在枚举之前或之后进行更改。

The forloop is a nice alternative, but if your IEnumerablecollection does not implement ICollection, it is not possible.

for循环是一个不错的选择,但如果你的IEnumerable收藏并没有实现ICollection,这是不可能的。

Either:

任何一个:

1) Copy collection first. Enumerate the copied collection and change the original collection during the enumeration. (@tvanfosson)

1)先复制收藏。枚举复制的集合并在枚举过程中更改原始集合。(@tvanfosson)

or

或者

2) Keep a list of changes and commit them after the enumeration.

2) 保留一份变更清单,并在枚举后提交。

回答by eulerfx

As mentioned, but with a code sample:

如前所述,但有一个代码示例:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);

回答by supercat

The best approach from a performance perspective is probably to use a one or two arrays. Copy the list to an array, do operations on the array, and then build a new list from the array. Accessing an array element is faster than accessing a list item, and conversions between a List<T>and a T[]can use a fast "bulk copy" operation which avoids the overhead associated accessing individual items.

从性能角度来看,最好的方法可能是使用一个或两个数组。将列表复制到数组,对数组进行操作,然后从数组构建新列表。访问数组元素比访问列表项更快,并且 aList<T>和 a之间的转换T[]可以使用快速“批量复制”操作,从而避免了访问单个项的相关开销。

For example, suppose you have a List<string>and wish to have every string in the list which starts with Tbe followed by an item "Boo", while every string that starts with "U" is dropped entirely. An optimal approach would probably be something like:

例如,假设您有 aList<string>并且希望列表中的每个字符串以T“Boo”开头,而每个以“U”开头的字符串都被完全删除。最佳方法可能是这样的:

int srcPtr,destPtr;
string[] arr;

srcPtr = theList.Count;
arr = new string[srcPtr*2];
theList.CopyTo(arr, theList.Count); // Copy into second half of the array
destPtr = 0;
for (; srcPtr < arr.Length; srcPtr++)
{
  string st = arr[srcPtr];
  char ch = (st ?? "!")[0]; // Get first character of string, or "!" if empty
  if (ch != 'U')
    arr[destPtr++] = st;
  if (ch == 'T')
    arr[destPtr++] = "Boo";
}
if (destPtr > arr.Length/2) // More than half of dest. array is used
{
  theList = new List<String>(arr); // Adds extra elements
  if (destPtr != arr.Length)
    theList.RemoveRange(destPtr, arr.Length-destPtr); // Chop to proper length
}
else
{
  Array.Resize(ref arr, destPtr);
  theList = new List<String>(arr); // Adds extra elements
}

It would have been helpful if List<T>provided a method to construct a list from a portion of an array, but I'm unaware of any efficient method for doing so. Still, operations on arrays are pretty fast. Of note is the fact that adding and removing items from the list does not require "pushing" around other items; each item gets written directly to its appropriate spot in the array.

如果List<T>提供一种从数组的一部分构造列表的方法会很有帮助,但我不知道这样做的任何有效方法。尽管如此,对数组的操作还是相当快的。值得注意的是,从列表中添加和删除项目不需要“推”其他项目;每个项目都直接写入数组中的相应位置。

回答by Timo

LINQis very effective for juggling with collections.

LINQ对于处理集合非常有效。

Your types and structure are unclear to me, but I will try to fit your example to the best of my ability.

我不清楚你的类型和结构,但我会尽力适应你的例子。

From your code it appears that, for each item, you are adding to that item everything from its own 'Enumerable' property. This is very simple:

从您的代码看来,对于每个项目,您都将其自己的“可枚举”属性中的所有内容添加到该项目中。这很简单:

foreach (var item in Enumerable)
{
    item = item.AddRange(item.Enumerable));
}

As a more general example, let's say we want to iterate a collection and remove items where a certain condition is true. Avoiding foreach, using LINQ:

作为一个更一般的例子,假设我们想要迭代一个集合并删除某个条件为真的项目。避免foreach,使用 LINQ:

myCollection = myCollection.Where(item => item.ShouldBeKept);

Add an item based on each existing item? No problem:

根据每个现有项目添加一个项目?没问题:

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));

回答by Roland Pihlakas

To illustrate Nippysaurus's answer: If you are going to addthe new items to the list and want to process the newly added items too during the same enumeration then you can just use forloop instead of foreachloop, problem solved :)

为了说明 Nippysaurus 的答案:如果您要将新项目添加到列表中并希望在同一枚举期间也处理新添加的项目,那么您可以使用for循环而不是foreach循环,问题解决了:)

var list = new List<YourData>();
... populate the list ...

//foreach (var entryToProcess in list)
for (int i = 0; i < list.Count; i++)
{
    var entryToProcess = list[i];

    var resultOfProcessing = DoStuffToEntry(entryToProcess);

    if (... condition ...)
        list.Add(new YourData(...));
}

For runnable example:

对于可运行示例:

void Main()
{
    var list = new List<int>();
    for (int i = 0; i < 10; i++)
        list.Add(i);

    //foreach (var entry in list)
    for (int i = 0; i < list.Count; i++)
    {
        var entry = list[i];
        if (entry % 2 == 0)
            list.Add(entry + 1);

        Console.Write(entry + ", ");
    }

    Console.Write(list);
}

Output of last example:

最后一个例子的输出:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 5, 7, 9,

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 3, 5, 7, 9,

List (15 items)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

列表(15 项)
0
1
2
3
4
5
6
7
8
9
1
3
5
7
9

回答by DDiVita

To add to Timo's answer LINQ can be used like this as well:

添加到 Timo 的答案 LINQ 也可以这样使用:

items = items.Select(i => {

     ...
     //perform some logic adding / updating.

     return i / return new Item();
     ...

     //To remove an item simply have logic to return null.

     //Then attach the Where to filter out nulls

     return null;
     ...


}).Where(i => i != null);