在“foreach”循环中修改列表的最好方法是什么?
C#/ .NET 4.0中的一个新function是,您可以在foreach
更改枚举而不会发生exception。 请参阅Paul Jackson的博客条目“并发的有意义的副作用:在枚举中删除项目”以获取有关此更改的信息。
什么是最好的方法来做到以下几点?
foreach(var item in Enumerable) { foreach(var item2 in item.Enumerable) { item.Add(new item2) } }
通常我使用IList
作为caching/缓冲直到foreach
结束,但有没有更好的方法?
foreach中使用的集合是不可变的。 这是非常devise。
正如它在MSDN上所说:
foreach语句用于遍历集合以获取所需信息,但不能用于从源集合添加或删除项目以避免不可预知的副作用。 如果您需要添加或删除源集合中的项目,请使用for循环。
Poko提供的链接中的post表示这允许在新的并发集合中。
制作一个枚举的副本,在这种情况下使用IEnumerable扩展方法,并枚举它。 这会将每个内部枚举中的每个元素的副本添加到该枚举中。
foreach(var item in Enumerable) { foreach(var item2 in item.Enumerable.ToList()) { item.Add(item2) } }
如上所述,但是有一个代码示例:
foreach(var item in collection.ToArray()) collection.Add(new Item...);
如果你真的需要这样的行为,你应该重新考虑你的devise,或者重写所有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); } } }
为了说明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,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,23,23,24,25,27,28,29,23,23,24,25,27,28,29,23,23,23,23,24,25,27,28,29,23,23,23,23,23,23,23,23,23,23,23,24,25,27,28,29,23,23,23,23,23,24,25
列表(15项)
0
1
2
3
4
五
6
7
8
9
1
3
五
7
9
在这种情况下,你应该使用for()而不是foreach()。
在枚举时,您不能更改枚举集合,因此您必须在枚举之前或之后进行更改。
for
循环是一个不错的select,但是如果你的IEnumerable
集合没有实现ICollection
,那是不可能的。
或者:
1)先复制收集。 枚举复制的集合并在枚举期间更改原始集合。 (@tvanfosson)
要么
2)保存一个更改列表,并在枚举后提交它们。
从性能angular度来看,最好的方法可能是使用一个或两个数组。 将列表复制到数组,对数组执行操作,然后从数组中构build一个新列表。 访问数组元素比访问列表项要快, List<T>
和T[]
之间的转换可以使用快速的“批量复制”操作,避免了访问单个项目的开销。
例如,假设您有一个List<string>
并且希望列表中每个以T
开头的string后跟一个“Boo”项,而以“U”开头的每个string都将被完全删除。 一个最佳的方法可能是这样的:
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>
提供了一个从数组的一部分构造列表的方法,但是我没有意识到这样做有效的方法,那将会很有帮助。 不过,对arrays的操作相当快。 值得注意的是,添加和删除列表中的项目不需要“推”其他项目; 每个项目被直接写入到数组中的适当位置。
LINQ对收集杂耍非常有效。
你们的种类和结构对我来说是不清楚的,但我会尽我所能去适应你的榜样。
从你的代码看来,对于每一个项目,你都要从它自己的“Enumerable”属性中添加所有项目。 这很简单:
foreach (var item in Enumerable) { item = item.AddRange(item.Enumerable)); }
作为一个更一般的例子,假设我们想迭代一个集合,并删除某些条件为真的条目。 避免使用LINQ:
myCollection = myCollection.Where(item => item.ShouldBeKept);
添加基于每个现有项目的项目? 没问题:
myCollection = myCollection.Concat(myCollection.Select(item => new Item(item.SomeProp)));