Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changes to an IEnumerable are not being kept between queries

IEnumerable is a query that is lazily evaluated. But apparently my understanding is a bit flawed. I'd expect the following to work:

        // e.Result is JSON from a server
        JObject data = JObject.Parse(e.Result);
        JsonSerializer serializer = new JsonSerializer();

        // LINQ query to transform the JSON into Story objects
        var stories = data["nodes"].Select(
                   obj => obj["node"]).Select(
                        storyData => storyOfJson(serializer, storyData));

        // set a value on each story returned by the query
        foreach (Story story in stories)
        {
            story.Vid = vid;
        }

        // run through the query again, making sure the value was actually set
        foreach (Story story in stories)
        {
            // FAILS - story.VID is 0
            Debug.Assert(story.Vid == vid);
        }

What am I misunderstanding here? How can I alter the results of what this query returns?

like image 744
Nick Heiner Avatar asked Nov 03 '10 02:11

Nick Heiner


3 Answers

Each time you enumerate the stories variable, the Select call runs again, creating a new set of Story objects.

Therefore, each foreach loop runs on a different set of Story instances.

You need to force the LINQ calls to evaluate exactly once by calling .ToArray().
Looping through the resulting array will not re-evaluate the LINQ calls (since it's a normal array), so you'll share the same set of Story instances.

like image 137
SLaks Avatar answered Oct 19 '22 07:10

SLaks


The IEnumerable in this case contains the return values of storyOfJson called repeatedly on the collection, not the original values.

When you enumerate the collection inside the foreach, the function is repeatedly called as necessary, and the result of the function call is placed into the foreach iteration variable (story in this case).

If you want to store the set of Story objects, you'll have to put them into some form of collection first, because the enumerated items are destroyed once the loop terminates.

Side Note: You should probably use the LINQ syntax rather than the functional syntax when possible.

var stories = data["nodes"].Select(obj => obj["node"]).Select(storyData => storyOfJson(serializer, storyData));

becomes

var stories = from node in data["nodes"]
              select storyOfJson(serializer, node["node"]);
like image 36
Billy ONeal Avatar answered Oct 19 '22 06:10

Billy ONeal


IEnumerable is supposed to be treated like a read-only collection. While some programs might return an existing collection, rather than a copy, other programs are free to return a new copy each time. For example, when you add .Select(storyData => storyOfJson(...)), you will get new objects each time.

This means that if you edit the copy you get, then perform the query/call get again, then your changes will be wiped out.

If you want to edit the results of the query, either add your own .Select() on top of the resulting query, to perform the transformation you are interested in, ala:

var filteredStoried = stories.Select(s => new Story(s) { Vid = vid });

foreach(var s in filteredStories)
    Assert(s.Vid == vid);

Or copy the query results to a local collection, and work with that:

var localStories = new List<Story>(stories);

foreach(var s in localStories)
    s.Vid = vid;

foreach(var s in localStories)
    Assert(s.Vid == vid);
like image 33
Merlyn Morgan-Graham Avatar answered Oct 19 '22 08:10

Merlyn Morgan-Graham