1

Let say I have a very simple reference type called Person.

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

I use LINQ to create an IEnumerable call peopleEnum with the following code.

var ages = new int[] { 1, 2, 3, 4, 5 };

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    {
        Name = a.ToString(),
        Age = a
    });

My understanding is because Person is reference type, the elements inside peopleEnum should be mutable. But I found out that they are not.

var person = peopleEnum.Single(p => p.Age == 4);
person.Name = "Four";

foreach (var p in peopleEnum)
{
    Console.WriteLine($"{p.Name}, {p.Age}");
}

Expected output.

2, 2
3, 3
Four, 4
5, 5

Actual output.

2, 2
3, 3
4, 4
5, 5

However, if I create IEnumerable<Person> in a different way.

var people = new Person[]
{
    new Person{ Name = "1", Age = 1 },
    new Person{ Name = "2", Age = 2 },
    new Person{ Name = "3", Age = 3 },
    new Person{ Name = "4", Age = 4 },
    new Person{ Name = "5", Age = 5 },
};

var peopleEnum2 = people.Where(p => p.Age > 1);

Then elements inside peopleEnum2 will be mutable.

var person = peopleEnum2.Single(p => p.Age == 4);
person.Name = "Four";

foreach (var p in peopleEnum2)
{
    Console.WriteLine($"{p.Name}, {p.Age}");
}

Output

2, 2
3, 3
Four, 4
5, 5

What rules determine whether elements inside an IEnumerable<T> are mutable or not?

duongntbk
  • 620
  • 4
  • 25
  • In your examples you enumerate the `IEnumerable` twice. When the `IEnumerable` is a concrete list or array, then enumerating multiple times yields the same elements each time. In your first example, the elements are created "on the fly" and "on demand" every time you enumerate, that's why you get different elements each time. – Corak Nov 25 '20 at 14:31
  • 1
    When you do `Single` it runs the code that creates the people objects. Then when you run the `foreach` it runs that code again creating 4 new objects. This is called deffered execution. – juharr Nov 25 '20 at 14:31

2 Answers2

1

It is because every time you iterate the peopleEnum enum,

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    {
        Name = a.ToString(),
        Age = a
    });

a new Person is created. While with the array they are created once (and put in the array). So in your case you iterate it and change the Person. And when you iterate it again a new person is created.

Magnus
  • 45,362
  • 8
  • 80
  • 118
1

There is distinction - this IEnumerable is based on ages array and executes each time you enumerate peopleEnum. That means you get new references each time you put your enumeration to foreach statement.

var ages = new int[] { 1, 2, 3, 4, 5 };

var peopleEnum = ages.Where(a => a > 1)
    .Select(a => new Person
    {
        Name = a.ToString(),
        Age = a
    });

In order to make elements "mutable" in first case, you have to store result to some static structure. For example, by using .ToList(). This helps to ensure you execute your Select statement only once and keep references produced by it

var peopleEnum = ages.Where(a => a > 1)
        .Select(a => new Person
        {
            Name = a.ToString(),
            Age = a
        }).ToList();
Yehor Androsov
  • 4,885
  • 2
  • 23
  • 40