I have already read answers in questions Update object in IEnumerable<> not updating? and Updating an item property within IEnumerable but the property doesn't stay set? but I would like to Understand why it is not working when I try to update an item in an IEnumerable.
What I don't understand is:
I cannot update an item of an Ienumerable using .ElementAt() method when the source collection is pointing to an Ienumerable.
However the same code works when the source Ienumerable is pointing to a list.
1 Could someone be kind to explain what happens behind the scenes?
2 Why C# compiler doesn't (or can't) error on this?
3 Also doesn't this invalidate using Ienumerable to define types when we have to convert Ienumerables to Lists whenever it needs updating?
I am sure I am missing something here I'd appriciate if someone can explain what's going on behind the scenes.
This is my code:
1 - Collection doesn't get updated:
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var numbers = new int[] {1,2,3};
var people = numbers.Select(n => new Person { Name = "Person " + n.ToString() });
for (int i = 0; i < people.Count(); i++)
{
people.ElementAt(i).Name = "New Name";
}
people.ToList().ForEach(i => Console.WriteLine(i.Name));
// OUTPUT: Person1, Person2, Person3
}
}
2 - Collection gets updated when I add .ToList() to the collection creation line (Note 3rd line in the Main method)
class Person
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var numbers = new int[] {1,2,3};
// Note the .ToList() at the end of this line.
var people = numbers.Select(n => new Person { Name = "Person " + n.ToString() }).ToList();
for (int i = 0; i < people.Count(); i++)
{
people.ElementAt(i).Name = "New Name";
}
people.ToList().ForEach(i => Console.WriteLine(i.Name));
Console.ReadLine();
// OUTPUT: New Name, New Name, New Name
}
}
As always, you need to remember that LINQ operations return queries, not the results of executing those queries.
When you call select what is returned is a query that knows how to project each item from the source collection into a new item each time an item is asked for. Each time you iterate the IEnumerable
you're executing the query a new time, creating a new result set (made up of all new items) each time. You can, and in fact are updating the items each time you call ElementAt
, which is why the compiler isn't stopping you. You're simply doing nothing with that item besides setting that one value and then dropping it on the floor, never to be used again. When you execute the line:
people.ElementAt(i).Name = "New Name";
You're iterating through numbers
, creating a new Person
for each number, until you get to the i-th value then it's returned from ElementAt
, at which point the name is set, and then that value is no longer accessible from your code because you haven't saved that returened Person
anywhere. When you call ElementAt(0)
you're creating a person from 0
, setting the name, then doing nothing. When you call ElementAt(1)
you're creating two people, returning the second, setting its name, then forgetting about it. Then you call ElementAt(2)
, creating 3 more people, returning the 3rd, setting its name, and then forgetting about it. Then you call ToList
, creating a list of all 3 people, and printing out the values of those 3 brand new people.
When you materialize the query into a collection, you can then access the items in that collection multiple times, because you have the results of a query that you're accessing multiple times, and keeping track of those same returned results, rather than a query itself that you're executing over and over again.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With