What is the best way to modify a list in a 'foreach' loop?

Viewed 155796

A new feature in C# / .NET 4.0 is that you can change your enumerable in a foreach without getting the exception. See Paul Jackson's blog entry An Interesting Side-Effect of Concurrency: Removing Items from a Collection While Enumerating for information on this change.

What is the best way to do the following?

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

Usually I use an IList as a cache/buffer until the end of the foreach, but is there better way?

11 Answers

The collection used in foreach is immutable. This is very much by design.

As it says on MSDN:

The foreach statement is used to iterate through the collection to get the information that you want, but can not be used to add or remove items from the source collection to avoid unpredictable side effects. If you need to add or remove items from the source collection, use a for loop.

The post in the link provided by Poko indicates that this is allowed in the new concurrent collections.

Make a copy of the enumeration, using an IEnumerable extension method in this case, and enumerate over it. This would add a copy of every element in every inner enumerable to that enumeration.

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

As mentioned, but with a code sample:

foreach(var item in collection.ToArray())
    collection.Add(new Item...);

You should really use for() instead of foreach() in this case.

You can't change the enumerable collection while it is being enumerated, so you will have to make your changes before or after enumerating.

The for loop is a nice alternative, but if your IEnumerable collection does not implement ICollection, it is not possible.

Either:

1) Copy collection first. Enumerate the copied collection and change the original collection during the enumeration. (@tvanfosson)

or

2) Keep a list of changes and commit them after the enumeration.

Here's how you can do that (quick and dirty solution. If you really need this kind of behavior, you should either reconsider your design or override all IList<T> members and aggregate the source list):

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

To add to Timo's answer LINQ can be used like this as well:

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

I have written one easy step, but because of this performance will be degraded

Here is my code snippet:-

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;
                                }
                            }
Related