Перебор динамически изменяющегося списка в C # - PullRequest
6 голосов
/ 01 февраля 2012

Мне нужно перебрать список (или любое другое перечисление), но я бы хотел добавить значения в список в ходе итерации.

Это пример.

public static void RunSnippet()
{
    List<int> hello = new List<int>();

    hello.Add(1); hello.Add(2); hello.Add(3);

    foreach (var x in hello)
    {
        Console.WriteLine(x);
        if (x == 1) {
            hello.Add(100);
        }
    }
}

Я ожидаю получить «1,2,3,100», но вместо этого я получил эту ошибку.

enter image description here

Как я могу перебрать список, который изменяется в процессе?

ДОБАВЛЕНО

Что я хочу сделать, так это чтобы я перебирал элементы для обработки чего-либо. Дело в том, что некоторые элементы должны быть разложены на подэлементы.

public static void RunSnippet()
{
    List<Element> hello = new List<Element>();

    hello.Add(Element); hello.Add(Element); hello.Add(Element);

    foreach (var x in hello)
    {
        List<Element> decomposed;
        decomposed = Decompose(x);
        if (decomposed != null) {
            foreach (var y in decomposed)
            {
                hello.Add(y);
            }
        }
    }
}

Ответы [ 7 ]

8 голосов
/ 01 февраля 2012

Вы не можете, в основном. Во всяком случае, не с петлей foreach. Вы можете использовать прямую петлю for:

for (int i = 0; i < hello.Count; i++)
{
    int x = hello[i];
    Console.WriteLine(x);
    if (x == 1) {
        hello.Add(100);
    }
}

Я бы лично постарался не делать этого в первую очередь - может быть очень трудно рассуждать о том, будете ли вы когда-нибудь завершать или пропустите элементы (если вы удаляете вместо добавления, или добавление до вашей текущей позиции).

4 голосов
/ 01 февраля 2012

Вы не можете. Вы должны создать новый список и сохранить там значения.

public static void RunSnippet()
{
    List<int> hello = new List<int>();
    List<int> additions = new List<int>();
    hello.Add(1); hello.Add(2); hello.Add(3);

    foreach (var x in hello)
    {
        Console.WriteLine(x);
        if (x == 1) {
            additions.Add(100);
        }
    }

    hello.AddRange(additions);
}
2 голосов
/ 01 февраля 2012

Используйте вместо этого снимок:

foreach (var x in hello.ToArray())
{
    // whatever here
}

Проблема решена! Ну, в некотором смысле. Элементы, добавленные во время итерации, не будут включены.

1 голос
/ 01 февраля 2012

Нет, вы не можете перебирать список и изменять их в одной и той же итерации.Вместо этого используйте новый список.

0 голосов
/ 01 февраля 2012

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

   public class CustomList : IEnumerable<int>
   {
      readonly List<int> list = new List<int>{1,2,3,4};
      private int now = 0;

      public void Add(int n)
      {
         list.Add(n);
      }

      public IEnumerator<int> GetEnumerator()
      {
         while (now<list.Count)
         {
            yield return list[now];
            now++;
         }
      }

      IEnumerator IEnumerable.GetEnumerator()
      {
         return GetEnumerator();
      }
   }

Теперь следующий фрагмент кода выведет на экран 1,2,3,4 и 100:

 var list = new CustomList();
 foreach (int n in list)
 {
    if(n==1)
       list.Add(100);
    Console.WriteLine(n);
 }

Но я пишу это только в качестве доказательства концепции. Вы не хотите делать это. Если вы будете добавлять только новые элементы на обороте, используйте Очередь, как говорили другие. Если вы всегда будете добавлять новые предметы на фронт, используйте Stack. Если вам понадобится и то и другое, напишите собственный класс LinkedList с операциями Dequeue (= Pop), Enqueue и Push, используйте что-то вроде:

while(list.notEmpty()) 
  var item = list.Dequeue();
  //bla bla

и все готово. (Вы могли бы даже написать собственный Enumerator снова, чтобы использовать его с foreach, но мы уничтожаем список по ходу работы, так что это противоречит духу перечислений, и зачем в любом случае беспокоиться)

0 голосов
/ 01 февраля 2012

Я обнаружил, что в C # есть стек. Я думаю, я мог бы использовать стек.

public static void RunSnippet()
{
    Stack<int> hello = new Stack<int>();
    hello.Push(1); hello.Push(2); hello.Push(3);

    while (hello.Count > 0)
    {
        int x = hello.Pop();
        Console.WriteLine(x);
        if (x == 1) {
            hello.Push(100);
        }
    }
}
0 голосов
/ 01 февраля 2012

Используя foreach, вы не можете!Вы можете использовать цикл for, но это очень плохой стиль, чтобы делать такие вещи.Подобные вещи делают ваш код очень подверженным ошибкам, непредсказуемым и трудным для отладки.

...