Каков наилучший способ изменить список в цикле "foreach"?
новая функция в C# / .NET 4.0 заключается в том, что вы можете изменить свой перечисляемый в foreach без исключения. Смотрите запись в блоге Пола Джексона интересный побочный эффект параллелизма: удаление элементов из коллекции при перечислении для получения информации об этом изменении.
каков наилучший способ сделать следующее?
foreach(var item in Enumerable)
{
foreach(var item2 in item.Enumerable)
{
item.Add(new item2)
}
}
обычно я использую IList как кэш/буфер до конца foreach, но там лучше путь?
10 ответов:
коллекция, используемая в foreach, является неизменяемой. Это очень много по дизайну.
Как говорится в MSDN:
оператор foreach используется для выполните итерацию по коллекции, чтобы получить информация, которую вы хотите, но можете не используется для добавления или удаления элементов из исходной коллекции, чтобы избежать непредсказуемые побочные эффекты. если вы нужно добавить или удалить элементы из исходная коллекция, использовать для петля.
должности в ссылке предоставленный Poko указывает, что это разрешено в новых параллельных коллекциях.
сделайте копию перечисления, используя в этом случае метод расширения 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...);
чтобы проиллюстрировать ответ Nippysaurus: если вы собираетесь добавить новые элементы в списке и хотите обрабатывать вновь добавленные элементы тоже во время того же перечисления, то вы можете просто использовать на петли вместо 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(...)); }для runnable примера:
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
вот как вы можете это сделать (быстрое и грязное решение. Если вы действительно нужно такое поведение, вы должны либо пересмотреть свой дизайн или переопределить все
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); } } }
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)));
вы не можете изменить перечисляемую коллекцию во время ее перечисления, поэтому вам придется внести изменения до или после перечисления.
The
forloop-хорошая альтернатива, но если вашIEnumerableколлекция не реализуетICollection, это невозможно.либо:
1)Сначала скопируйте коллекцию. Перечислите скопированную коллекцию и измените исходную коллекцию во время перечисления. (@tvanfosson)
или
2) сохраните список изменений и зафиксируйте их после перечисления.
лучший подход с точки зрения производительности, вероятно, использовать один или два массива. Скопируйте список в массив, выполните операции над массивом, а затем создайте новый список из массива. Доступ к элементу массива выполняется быстрее, чем к элементу списка, и преобразования между
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>предоставил метод для построения списка из части массива, но я не знаю ни одного эффективного метода для этого. Тем не менее, операции с массивами выполняются довольно быстро. Следует отметить тот факт, что добавление и удаление элементов из списка не требуют "тужиться" вокруг других элементов; каждый элемент записывается непосредственно в соответствующее место в массиве.
чтобы добавить к ответу Тимо 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);
Comments