C#/ .NET 4.0中的一个新功能是,您可以在foreach
中更改您的枚举,而不会出现异常。有关此更改的信息,请参阅Paul Jackson的博客条目 An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumerating 。
执行以下操作的最佳方式是什么?
foreach(var item in Enumerable)
{
foreach(var item2 in item.Enumerable)
{
item.Add(new item2)
}
}
通常我会使用IList
作为缓存/缓冲区,直到foreach
结束,但有更好的方法吗?
答案 0 :(得分:66)
foreach中使用的集合是不可变的。这非常符合设计。
正如MSDN所说:
foreach语句习惯了 迭代整个集合来获取 你想要的信息,但可以 不得用于添加或删除项目 从源集合中避免 不可预知的副作用。 如果你 需要添加或删除项目 源集合,使用for循环。
Poko提供的link中的帖子表明在新的并发集合中允许这样做。
答案 1 :(得分:15)
在这种情况下使用IEnumerable扩展方法复制枚举,并枚举它。这会将每个内部可枚举元素中的每个元素的副本添加到该枚举中。
foreach(var item in Enumerable)
{
foreach(var item2 in item.Enumerable.ToList())
{
item.Add(item2)
}
}
答案 2 :(得分:7)
如上所述,但使用代码示例:
foreach(var item in collection.ToArray())
collection.Add(new Item...);
答案 3 :(得分:7)
说明Nippysaurus的答案:如果您要将添加新项目添加到列表中,并希望在同一枚举中处理新添加的项目,那么您只需使用 for < / strong>循环而不是 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
答案 4 :(得分:4)
以下是如何做到这一点(快速而肮脏的解决方案。如果您真的需要这种行为,您应该重新考虑您的设计或覆盖所有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);
}
}
}
答案 5 :(得分:2)
LINQ对于杂耍收藏非常有效。
我的类型和结构对我来说并不清楚,但我会努力尽力满足你的榜样。
从您的代码中可以看出,对于每个项目,您都要将该项目添加到其自身的“可枚举”项目中。属性。这很简单:
foreach (var item in Enumerable)
{
item = item.AddRange(item.Enumerable));
}
作为一个更一般的例子,让我们说我们想要迭代一个集合并删除某个条件为真的项目。使用LINQ避免foreach
:
myCollection = myCollection.Where(item => item.ShouldBeKept);
根据每个现有项目添加项目?没问题:
myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));
答案 6 :(得分:1)
在枚举时,您无法更改可枚举的集合,因此您必须在枚举之前或之后进行更改。
for
循环是一个不错的选择,但如果您的IEnumerable
集合未实现ICollection
,则无法实现。{/ p>
要么:
1)首先复制集合。枚举复制的集合并在枚举期间更改原始集合。 (@tvanfosson)
或
2)保留一个更改列表,并在枚举后提交它们。
答案 7 :(得分:1)
从性能角度来看,最好的方法可能是使用一个或两个数组。将列表复制到数组,对阵列执行操作,然后从阵列中构建新列表。访问数组元素比访问列表项更快,List<T>
和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>
提供了一种从数组的一部分构造列表的方法,那将会很有帮助,但我不知道这样做的任何有效方法。尽管如此,对阵列的操作还是非常快的。值得注意的是,从列表中添加和删除项目不需要“推”其他项目;每个项目都直接写入数组中的适当位置。
答案 8 :(得分:0)
在这种情况下,您应该使用for()
代替foreach()
。
答案 9 :(得分:0)
要添加到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);
答案 10 :(得分: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;
}
}