4

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
    }

}
Community
  • 1
  • 1
Menol
  • 1,230
  • 21
  • 35
  • 4
    What don't you understand about the answer to http://stackoverflow.com/questions/7304372/? Basically, each time you iterate over `people` in your first piece of code, it will create new instances of `Person` - the objects you modified are basically lost. – Jon Skeet May 10 '16 at 14:37
  • Check out deferred execution. Here is an example. Check the details because it's tricky. https://msdn.microsoft.com/en-us/library/mt693185.aspx – Stefan May 10 '16 at 14:39
  • @Jon Skeet thanks for the quick response. When I write people.ElementAt(0).Name= "New name" shouldn't it update the the same object (reference type) regardless to what it belongs to? – Menol May 10 '16 at 14:41
  • 1
    @Menol: No, because `ElementAt()` has to start iterating over the sequence in order to get the value. You need to read up on LINQ's lazy evaluation, basically. – Jon Skeet May 10 '16 at 14:42
  • 1
    I'm willing to bet that the objects inside the enumerable are made by value and not by reference which would explain why when you modify the object in the enumerable it fails to stick but in the list on the other hand is not by value but rather by reference. – Richard Barker May 10 '16 at 14:44
  • It's the Select( new ) that's causing the problem. You're working with a new enumeration every time the ElementAt() is called. – Spivonious May 10 '16 at 14:44
  • 1
    @RichardBarker: You'd need to specify what *exactly* you mean by "made by value" for that to be reasonable. Fundamentally, the problem is a lack of understanding of lazy evaluation. – Jon Skeet May 10 '16 at 14:44
  • 1
    @Menol - add a property to your person for DateCreated = DateTime.Ticks and you'll see that your ElementAt Person does not equal your looping Person. – Spivonious May 10 '16 at 14:45
  • @Spivonious why would that be the case? are you saying the linq reruns the query every time you access the enumerable.elementAt? – Richard Barker May 10 '16 at 14:45
  • @JonSkeet I'm sorry but I still dont get this. even if the ElementAt() had to ierate through everytime to get an item, once we get the reference to the 0th person object then shouldn't all changes made to it applied to the Person object it is pointing to? people.ElementAt(0).Name = "New Name"; Why doesn't this update the actual object that is accessed when the Ienumerable will return when I ask for the 0th object again? – Menol May 10 '16 at 14:46
  • @RichardBarker - yes, exactly. – Spivonious May 10 '16 at 14:46
  • 1
    @Menol: Because *every time you ask the sequence for items, it evaluates again from scratch*. Again, I encourage you to read up on LINQ and lazy evaluation. – Jon Skeet May 10 '16 at 14:47
  • @JonSkeet is there value type reference involved here? otherwise if I'm not mistaken regardless of how we find an object, any changes to it affects the same memory that represents the object. – Menol May 10 '16 at 14:47
  • @JonSkeet I mean the object is essentially remade in memory. Much in the same way string variables are remade when you pass them around. Theres a term for that kind of variable but I can't recall it. – Richard Barker May 10 '16 at 14:48
  • 1
    The 1st method iterates over the int[] and generates a new Person object every time you ask it for an element. The 2nd method iterates over the int[] just once and creates a List of Person objects from it. Every time after that you iterate, you are iterating over that List. – Dennis_E May 10 '16 at 14:48
  • 3
    @Menol: "Value type reference" isn't a meaningful term. What you're missing is lazy evaluation. – Jon Skeet May 10 '16 at 14:48
  • @RichardBarker: Variables aren't "remade" - I'm afraid you'll need to be more precise with exactly what you mean, but it's not a matter of the types involved - it's just the behaviour of LINQ. – Jon Skeet May 10 '16 at 14:49
  • 2
    Hint: Give the Person class a constructor that prints a line to the console and see how many times a Person object is created. – Dennis_E May 10 '16 at 14:50
  • @JonSkeet I will definitely read more on Linq and lazy evaluation. I'm definitely missing something in this area. – Menol May 10 '16 at 14:54
  • your `person` which is IEnumerable doesnt hold any items. it just generates items. when you call `ToList` you actually save them inside list – M.kazem Akhgary May 10 '16 at 14:54
  • @JonSkeet You're failing to understand what I'm saying simply due to my terminology - I know there's a term for these kinds of types I just can't recall it at the moment. But it seems that the question has been answered. – Richard Barker May 10 '16 at 14:54
  • Yes, I think it's a fundamental misunderstanding of enumerators. They are not data structures, even though you can use them in similar ways to array, list, etc.. – Spivonious May 10 '16 at 14:55
  • well doesn't this bring to my 3rd point? I am surprised to learn that I have to convert an Ienumerable to a concrete type whenever I need to update it. any thoughts on this? – Menol May 10 '16 at 14:55
  • 2
    IEnumerable has other uses. for example having 1 million items in disk and trying to store them all in list may screw up your memory. while IEnumerable works with items one after another. so you can save a lot of memory. – M.kazem Akhgary May 10 '16 at 14:58
  • 1
    `List` returns an enumerator that is moving over an array. `Select()` returns an enumerator that is creating new objects as they are needed. It's the source of the data that's different. – Spivonious May 10 '16 at 14:58
  • @M.kazemAkhgary I was thinking the same way but what's the use of it if I cann't update an item without converting the Ienumerable to a concrete class anyway? – Menol May 10 '16 at 14:59
  • @RichardBarker Sorry I missed your comments in the quick rush of many comments. Can you please elaborate a little more on your comment about involving value types behind Ienumerable? I understand if you can't remember everything. Even a little nudge in the right direction would help. – Menol May 10 '16 at 15:01
  • in the case i given (loading items from hard disk) you can load one item with enumerable. update it. save it back to disk and move to next item. kinda same idea for other cases such as working with database... – M.kazem Akhgary May 10 '16 at 15:02
  • 3
    @Menol You don't *mutate* items, you transform each sequence into a new sequence. Rather than projecting items into mutable types and then mutating them, you project each item in the sequence into a new (immutable) item, and then any future operations act on those new items (returning new items of their own, if needed), without any knowledge or interest in where they came from. – Servy May 10 '16 at 15:02
  • 2
    Use an emumerable for read only operations and use lists or other types of collections when you need to write. I use enumerables only to iterate and read over a collection in a way that prevents any modification to the underlying elements. If elements need to be modified then i usually use a generic list. – Richard Barker May 10 '16 at 15:02
  • @Servy thank you! You reminded me of the terms! Enumerables are Immutable objects while lists are not. Much in the same way a string is an immutable object. when ever you change the value of a string variable (as I understand it) you are reintalizing the variable. – Richard Barker May 10 '16 at 15:03
  • @M.kazemAkhgary Whether the types are value types or reference types is irrelevant here. It's a very separate concept from those at play here. – Servy May 10 '16 at 15:03
  • 2
    @RichardBarker enumerable objects are virtually never immutable. About the only times you'd see it are for an empty enumerator or a enumerator that generates new values on each iteration without needing any state to generate the next value (which is rare). – Servy May 10 '16 at 15:05
  • @Servy only two questions asked and almost 150K rep. How long have you been doing this? 0.O lol thank you for clarifying – Richard Barker May 10 '16 at 15:09
  • @RichardBarker I think the secret is behind > 4.1k answers he has provided :) – Menol May 11 '16 at 08:23

2 Answers2

10

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.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • I understand Linq returns queries. However the bottom line is that when I say people.ElementAt(0).Name = "New Name" the object gets updated when the returned query is pointing to an object in a concrete collection. But it doesn't work when the underlying collection is simply an Ienumerable (or not defined) Surely the framework is still storing those objects somewhere in the memory but they cant be updated. Doest this mean framework stores underlying collection for Ienumerable (case 1 in example) as value types or something similar? – Menol May 10 '16 at 15:29
  • 1
    The framework is NOT storing those objects somewhere unless you tell it to (e.g. by building a List from the enumeration). Every time you call ElementAt(0).Name, it's re-creating the Person objects up to index 0. That object is then updated to "New Name", and then the object is thrown away. – Spivonious May 10 '16 at 15:41
  • I was talking about how the framework stores values in case one --> var people = numbers.Select(n => new Person { Name = "Person " + n.ToString() }); THERE'S NO toList or toArray – Menol May 10 '16 at 16:33
  • @Menol Again, `people` is **a query**, not the results of that query. All that is stored in that object is a set of instructions that describe how one can get the results when asked for them. They don't exist yet. (This is why `people` is a bad name, you're not storing people, you're storing a query that can get you people later.) – Servy May 10 '16 at 16:38
  • thanks @Servy for your time. I still dont understand why the same query allow the list it is pointing to be updated in case 2 but not in case 1. However, I found plenty of leads thanks to you and others to investigate further. – Menol May 11 '16 at 08:21
  • 1
    @Menol It's **a query**, it's **not a list** that it's pointing to. In one case you actually have a list, and in another you don't have a list at all, you have a query, the two are radically different concepts, and you're treating them as if they're the same. – Servy May 11 '16 at 12:57
-4

IEnumerable only expose one method: GetEnumerator(), wich can be used to iterate over the list. ICollection instead expose an Add() method, wich can be used to add items to the collection. There is no way to add items to ICollection, only to iterate over it.

"Enumerators can be used to read the data in the collection, but they cannot be used to modify the underlying collection."

https://msdn.microsoft.com/en-us/library/system.collections.ienumerator(v=vs.100).aspx

https://msdn.microsoft.com/en-us/en-en/library/system.collections.icollection(v=vs.110).aspx

https://msdn.microsoft.com/en-us/en-en/library/system.collections.ienumerable(v=vs.110).aspx

Oscar
  • 13,594
  • 8
  • 47
  • 75
  • You've missed the point of the question - the OP isn't trying to add anything; they're trying to modify the objects. That in itself is fine - but each time the query is iterated over, it re-runs, creating a new set of objects. – Jon Skeet May 10 '16 at 14:37