13
string newName = "new name";

int[] numbers = new int[] { 1, 2, 3 };

var people = numbers.Select(n => new Person()
{
    Name = n.ToString()
});

foreach (var person in people)
{
    person.Name = newName;
}

Debug.WriteLine(people.First().Name == newName); // returns false

I expected the above line to return true. Why can't I set properties of iteration variables inside a foreach loop?

amit_g
  • 30,880
  • 8
  • 61
  • 118
Yeonho
  • 3,629
  • 4
  • 39
  • 61
  • 3
    duplicate of: http://stackoverflow.com/questions/776430/why-is-the-iteration-variable-in-a-c-sharp-foreach-statement-read-only – M.Babcock Feb 03 '12 at 02:39
  • 4
    That is not a valid duplicate, this is not about assigning to the loop variable. This is a problem of deferred execution. – Anthony Pegram Feb 03 '12 at 02:44
  • @M.Babcock I don't think it's a duplicate question. the question in the link tries to assign to the iteration variable itself, whereas my question assigns to the iteration varaible's property. but thanks! – Yeonho Feb 03 '12 at 02:45
  • 1
    @M.Babcock, it is not a dup of that question. That question is about modifying the iterator itself and that can't be done because the iterator is immutable. However nothing stops one from modifying the content of the iterator (see http://stackoverflow.com/a/776780/495041). The reason for this behavior is that the variable people is IQuerable which is executed once when the foreach loop it run and again when the First() is invoked, as mentioned in http://stackoverflow.com/a/9123015/495041 – amit_g Feb 03 '12 at 02:47

2 Answers2

20

people is a query definition with deferred execution. Your foreach over the query is irrelevant, this isn't about an inability to set a property. When you invoke First(), you run the query again.

To be clear, the query definition here is that for the elements in numbers, create a new Person and assign the value of the current number element to the Person's Name property. When you iterate in the foreach loop, the query evaluates, and you create new Person objects. But those Person objects are not in the query, it's just a definition! Running the query again executes the definition again, creating different Person objects. The fact that you modified the query's original results does not impact the second set of results.

If you would like an immediate execution, use

var people = numbers.Select(n => new Person() 
    { 
        Name = n.ToString() 
    }).ToList(); 

You'll find your changes in the loop sticking, because now people is a concrete list instead of a query definition.

foreach (var person in people) 
{ 
     person.Name = newName; 
} 

Debug.WriteLine(people.First().Name.Equals(newName)); // returns true
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
6

This is a perfect example of deferred execution.

If you try this example it works as you expect since the ToList executes the query.

    string newName = "new name";

    int[] numbers = new int[] { 1, 2, 3 };

    var people = numbers.Select(n => new Person()
    {
        Name = n.ToString()
    }).ToList(); // <===== here

    foreach (var person in people)
    {
        person.Name = newName;
    }

    var b = people.First().Name == newName;
Lester
  • 4,243
  • 2
  • 27
  • 31