Каков наилучший способ изменить список в цикле 'foreach'?

StackOverflow https://stackoverflow.com/questions/759966

Вопрос

Новая функция в C # / .NET 4.0 заключается в том, что вы можете изменить свой enumerable в foreach не получая исключения.Смотрите запись в блоге Пола Джексона Интересный побочный эффект параллелизма:Удаление элементов из коллекции при перечислении для получения информации об этом изменении.

Каков наилучший способ сделать следующее?

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

Обычно я использую IList в качестве кэша / буфера до конца foreach, но есть ли способ получше?

Это было полезно?

Решение

Коллекция, используемая в foreach, является неизменяемой.Это во многом сделано специально.

Как сказано на MSDN:

Оператор foreach используется для перебора коллекции, чтобы получить нужную информацию, но не может использоваться для добавления или удаления элементов из исходной коллекции, чтобы избежать непредсказуемых побочных эффектов. Если вам нужно добавить или удалить элементы из исходной коллекции, используйте цикл for.

Сообщение в Ссылка предоставленный 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...);

Чтобы проиллюстрировать ответ Ниппизавра:Если вы собираетесь добавлять новые элементы в список и вы хотите обработать вновь добавленные элементы во время того же перечисления, тогда вы можете просто использовать для цикл вместо для каждого цикл, проблема решена :)

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

Вот как вы можете это сделать (быстрое и грязное решение.Если вы Действительно нужно такое поведение, вам следует либо пересмотреть свой дизайн, либо переопределить все 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);
        }
    }
}

ЛИНК очень эффективен для жонглирования коллекциями.

Ваши типы и структура мне непонятны, но я постараюсь в меру своих возможностей подогнать ваш пример.

Из вашего кода видно, что для каждого элемента вы добавляете к этому элементу все из его собственного свойства Enumerable.Это очень просто:

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

Вы не можете изменить перечисляемую коллекцию во время ее перечисления, поэтому вам придется внести свои изменения до или после перечисления.

Тот Самый for цикл - хорошая альтернатива, но если ваш 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> предоставил метод создания списка из части массива, но мне неизвестен какой-либо эффективный способ сделать это.Тем не менее, операции с массивами выполняются довольно быстро.Следует отметить тот факт, что добавление и удаление элементов из списка не требует «перетаскивания» других элементов;каждый элемент записывается непосредственно в соответствующее место массива.

Вам действительно следует использовать for() вместо foreach() в этом случае.

Чтобы добавить к ответу Тимо, 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);

Я написал один простой шаг, но из-за этого производительность ухудшится.

Вот мой фрагмент кода: -

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;
                                }
                            }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top