For Loop Variable Within IEnumerable Changes Values Outside of Scope

Demitrius Wheelwright 21 Reputation points
2021-10-30T14:31:13.01+00:00

I am experiencing some odd behavior that I am not understanding.

The code sample below illustrates the problem.

When using a for loop and using the variable to set a property on an object - I store that object into the list.

When the for loop variable increments, the property values for the objects that are stored inside the list change as well.

Here is the code sample

List<IEnumerable<string>> data = new List<IEnumerable<string>>()
            {
                new List<string> { "Item1", "Item2", "Item3", "Item4" },
                new List<string> { "Item5", "Item6", "Item7", "item8" },
                new List<string> { "Item9", "Item10", "Item11", "Item12" }
            };

            List<IEnumerable<Item>> itemRows = new List<IEnumerable<Item>>();

            for (int i = 0; i < data.Count; i++)
            {
                var columns = data.ElementAt(i);

                var items = columns.Select((value, index) => new { Value = value, Index = index })
                    .Select(x => new Item { Index = i });

                itemRows.Add(items);

                Console.WriteLine($"Value of 'i': {i}");
                Console.WriteLine(string.Join("\n", itemRows.Select(x => string.Join("-", x.Select(y => y.Index)))) + "\n");
            }

And here is what it produces:

Value of 'i': 0
0-0-0-0

Value of 'i': 1
1-1-1-1
1-1-1-1

Value of 'i': 2
2-2-2-2
2-2-2-2
2-2-2-2

If I call .ToList() on the IEnumerable or even assign 'i' to a new variable and then use that, the behavior goes away:

List<IEnumerable<string>> data = new List<IEnumerable<string>>()
            {
                new List<string> { "Item1", "Item2", "Item3", "Item4" },
                new List<string> { "Item5", "Item6", "Item7", "item8" },
                new List<string> { "Item9", "Item10", "Item11", "Item12" }
            };

            List<IEnumerable<Item>> itemRows = new List<IEnumerable<Item>>();

            for (int i = 0; i < data.Count; i++)
            {
                var columns = data.ElementAt(i);

                var items = columns.Select((value, index) => new { Value = value, Index = index })
                    .Select(x => new Item { Index = i });

                itemRows.Add(items);

                Console.WriteLine($"Value of 'i': {i}");
                Console.WriteLine(string.Join("\n", itemRows.Select(x => string.Join("-", x.Select(y => y.Index)))) + "\n");
            }

Why is it only with an IEnumerable and goes when to calling .ToList()

Developer technologies C#
{count} votes

Accepted answer
  1. P a u l 10,761 Reputation points
    2021-10-30T15:22:24.603+00:00

    This is because IEnumerables are evaluated only when you perform an operation on them that forces them to become concreted. You can store a collection of IEnumerable, all of which have .Select() operations that use a value from the current scope (in this case i) and it may be different depending on what value it holds at the point that it's evaluated.

    Every iteration of your loop ends with a call to string.Join which forces what's in itemRows to become concrete and evaluate the value of i, and that includes all previous entries in itemRows, so the value of i will just be what it is at that point in time for all IEnumerables in the list. This is why every row refers to the current value of i and not the value that i had when the .Select() operation was declared.

    If you .ToList() on items before adding to itemRows then you'll have a List<List<Item>> that you'll be covariant-ly referring to by List<IEnumerable<Item>>. This means that the .Select() on items is evaluated/concreted once before adding to itemRows, rather than on every call to string.Join.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.