查询之间不保留对IEnumerable的更改

时间:2010-11-03 02:23:24

标签: c# .net linq ienumerable

IEnumerable是一个懒惰评估的查询。但显然我的理解有点瑕疵。我期待以下工作:

        // 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);
        }

我在这里误解了什么?如何更改此查询返回的结果?

3 个答案:

答案 0 :(得分:11)

每次枚举stories变量时,Select调用都会再次运行,从而创建一组新的Story个对象。

因此,每个foreach循环都在不同的Story个实例集上运行。

您需要强制LINQ调用通过调用.ToArray()来评估一次 循环生成的数组不会重新评估LINQ调用(因为它是一个普通的数组),所以你将共享同一组Story个实例。

答案 1 :(得分:1)

在这种情况下,IEnumerable包含在集合上重复调用的storyOfJson的返回值,而不是原始值。

当您在foreach中枚举集合时,会根据需要重复调​​用该函数,并将函数调用的结果放入foreach迭代变量(story中情况)。

如果要存储Story个对象的集合,首先必须将它们放入某种形式的集合中,因为一旦循环终止,枚举的项目就会被销毁。

备注:如果可能,您应该使用LINQ语法而不是函数语法。

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

变为

var stories = from node in data["nodes"]
              select storyOfJson(serializer, node["node"]);

答案 2 :(得分:1)

IEnumerable应该被视为只读集合。虽然某些程序可能会返回现有的集合而不是副本,但其他程序每次都可以自由返回新的副本。例如,当您添加.Select(storyData => storyOfJson(...))时,每次都会获得新对象。

这意味着如果你编辑你得到的副本,然后再次执行查询/调用get,那么你的更改将被删除。

如果要编辑查询结果,请在生成的查询之上添加自己的.Select(),以执行您感兴趣的转换,ala:

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

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

或者将查询结果复制到本地集合,并使用它:

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

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

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