Friday, April 29, 2011

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

Hello, 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 for info on this change.

So I'm asking: what is the best way to do this:

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

Usualy I use a IList as a cache/buffer until the end of the foreach but I'm sure there is better way :)

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

    Anton Gogolev : That will potentially clutter code with one more variable which will hold the original number of elements in collection.
    ck : If you are changing the length of the collection, then you want your for loop to be evaluated against the new length, not the original or you could get an out of range exception
    Rik : @Anton: You will *need* that extra variable, because you'll have to manage the iteration yourself when the collection is modified
    Josh G : @Nippysaurus: Sounds great, and I agree with you in theory, but some collections can't be indexed. You have to iterate over them.
  • 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.

    Josh G : You're answer doesn't really answer the question. He knows that the basic foreach loop is immutable... he wants to know the best way to apply changes to an enumerable collection.
    Rik : Then the answer is: use a regular for-loop, as suggested in the quote. I am aware that the OP mentions this behavior changes in C# 4.0, but I can't find any information on that. For now, I think this is still a relevant answer.
    Polo : http://www.lovethedot.net/2009/03/interesting-side-effect-of-concurrency.html
  • 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)
        }
    }
    
  • Here's how you can do that (quick'n'dirty solution, if you really need this kind of behavior you should either reconsider your design or override all IList<T> members and aggregate 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);
            }
        }
    }
    
  • 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.

  • This is a variation on the theme that prompted Steve McConnell to advise never to monkey with the loop index.

  • As mentioned, but with a code sample:

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

0 comments:

Post a Comment