96

C# / .NET 4.0 中的一个新特性是您可以在 a 中更改您的枚举foreach而不会出现异常。有关此更改的信息,请参阅 Paul Jackson 的博客条目并发的有趣副作用:在枚举时从集合中删除项目。

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

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

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

4

11 回答 11

86

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

正如MSDN上所说:

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

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

于 2009-04-17T10:56:17.700 回答
15

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

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}
于 2009-04-17T10:56:48.837 回答
10

为了说明 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(...));
}

对于可运行的示例:

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);
}

最后一个例子的输出:

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

于 2015-08-26T12:04:23.040 回答
8

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

foreach(var item in collection.ToArray())
    collection.Add(new Item...);
于 2009-04-28T23:36:16.397 回答
4

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

于 2009-04-17T10:51:32.033 回答
4

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

for循环是一个不错的选择,但如果你的集合IEnumerable没有实现ICollection,那就不可能了。

任何一个:

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

或者

2)保留更改列表并在枚举后提交。

于 2009-04-17T12:17:12.763 回答
3

这是您可以做到的(快速而肮脏的解决方案。如果您真的需要这种行为,您应该重新考虑您的设计或覆盖所有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);
        }
    }
}
于 2009-04-17T11:02:43.223 回答
3

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

你的类型和结构对我来说不清楚,但我会尽我所能来适应你的例子。

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

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

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

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

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

myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
于 2015-03-30T08:53:51.080 回答
1

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

例如,假设您有一个List<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
}

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

于 2013-01-27T19:05:03.240 回答
1

要添加到蒂莫的答案 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);
于 2018-09-21T12:50:39.447 回答
0

我已经写了一个简单的步骤,但是因为这个性能会下降

这是我的代码片段:-

for (int tempReg = 0; tempReg < reg.Matches(lines).Count; tempReg++)
                            {
                                foreach (Match match in reg.Matches(lines))
                                {
                                    var aStringBuilder = new StringBuilder(lines);
                                    aStringBuilder.Insert(startIndex, match.ToString().Replace(",", " ");
                                    lines[k] = aStringBuilder.ToString();
                                    tempReg = 0;
                                    break;
                                }
                            }
于 2018-10-30T09:13:17.073 回答