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

c# .net list enumeration enumerable

101774 просмотра

11 ответа

817 Репутация автора

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

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

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

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

Автор: Polo Источник Размещён: 17.04.2009 10:48

Ответы (11)


0 плюса

10894 Репутация автора

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

Автор: Nippysaurus Размещён: 17.04.2009 10:51

69 плюса

23010 Репутация автора

Решение

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

Как говорится в MSDN :

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

Сообщение в ссылке, предоставленной Poko, указывает, что это разрешено в новых одновременных коллекциях.

Автор: Rik Размещён: 17.04.2009 10:56

15 плюса

446632 Репутация автора

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

foreach(var item in Enumerable)
{
    foreach(var item2 in item.Enumerable.ToList())
    {
        item.Add(item2)
    }
}
Автор: tvanfosson Размещён: 17.04.2009 10:56

4 плюса

94836 Репутация автора

Вот как вы можете это сделать (быстрое и грязное решение. Если вам действительно нужно такое поведение, вам следует либо пересмотреть свой дизайн, либо переопределить все 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);
        }
    }
}
Автор: Anton Gogolev Размещён: 17.04.2009 11:02

1 плюс

11379 Репутация автора

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

forПетля является хорошей альтернативой, но если ваша IEnumerableколлекция не реализует ICollection, не представляется возможным.

Или:

1) Сначала скопируйте коллекцию. Перечислите скопированную коллекцию и измените исходную коллекцию во время перечисления. (@Tvanfosson)

или же

2) Вести список изменений и фиксировать их после перечисления.

Автор: Josh G Размещён: 17.04.2009 12:17

8 плюса

30681 Репутация автора

Как уже упоминалось, но с примером кода:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);
Автор: eulerfx Размещён: 28.04.2009 11:36

1 плюс

60932 Репутация автора

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

Автор: supercat Размещён: 27.01.2013 07:05

2 плюса

4088 Репутация автора

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

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

Из вашего кода видно, что для каждого элемента вы добавляете к этому элементу все из его собственного свойства 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)));
Автор: Timo Размещён: 30.03.2015 08:53

7 плюса

2586 Репутация автора

Чтобы проиллюстрировать ответ 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

Автор: Roland Pihlakas Размещён: 26.08.2015 12:04

0 плюса

2533 Репутация автора

Чтобы добавить к ответу Тимо 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);
Автор: DDiVita Размещён: 21.09.2018 12:50

0 плюса

1 Репутация автора

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

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

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;
                                }
                            }
Автор: pravin ghare Размещён: 30.10.2018 09:13
32x32