为什么在 foreach 中对 Linq 分组 select 所做的更改会被忽略,除非我添加 ToList()?

Why do changes made in foreach to a Linq grouping select get ignored unless I add ToList()?

我有以下方法

public IEnumerable<Item> ChangeValueIEnumerable()
    {
        var items = new List<Item>(){
            new Item("Item1", 1),
            new Item("Item2", 1),
            new Item("Item3", 2),
            new Item("Item4", 2),
            new Item("Item5", 3)
        };

        var groupedItems = items.GroupBy(i => i.Value)
            .Select(x => new Item(x.First().Name, x.Key));

        foreach (var item in groupedItems)
        {
            item.CalculatedValue = item.Name + item.Value;
        }

        return groupedItems;
    }

groupedItems 集合中 CalculatedValue 为空。但是,如果我在 GroupBy 之后的 Select 句子中添加 ToList(),则 CalculatedValues 具有值。 例如:

 var groupedItems = items.GroupBy(i => i.Value)
            .Select(x => new Item(x.First().Name, x.Key)).ToList();

那么,问题来了。为什么是这样?我想知道这是什么原因,我的解决方案是添加一个 ToList()

更新:Itemclass的定义如下

 public class Item
{
    public string Name { get; set; }
    public int Value { get; set; }

    public string CalculatedValue { get; set; }

    public Item(string name, int value)
    {
        this.Name = name;
        this.Value = value;
    }
}
var groupedItems = items.GroupBy(i => i.Value)
    .Select(x => new Item(x.First().Name, x.Key));

在这里,groupedItems实际上没有任何物品。 Select 返回的 IEnumerable<T> 表示一个计算 - 准确地说,它表示通过应用函数 x => new Item(x.First().Name, x.Key).[= 将 items 映射到一组新项目的结果。 25=]

每次迭代 groupedItems 时,都会应用该函数并创建一组新的项目。

var groupedItems = items.GroupBy(i => i.Value)
    .Select(x => 
    {
        Console.WriteLine("Creating new item");
        return new Item(x.First().Name, x.Key));
    }

foreach(var item in groupedItems);
foreach(var item in groupedItems);

例如,此代码将为 items 中的每个项目打印两次 "Creating new item"。

在您的代码中,您正在设置 临时 项目的 CalculatedValue。当 foreach 循环完成后,项目就消失了。

通过调用 ToList,您将 "computation" 变成一个实际的项目集合。

除了调用 ToList,您还可以创建 另一个计算 来表示一组新的项目及其 CalculatedValue 属性 集.这是 功能性 方式。

Func<Item, Item> withCalculatedValue =
    item => {
        item.CalculatedValue = item.Name + item.Value;
        return item;
    };

return items.GroupBy(i => i.Value)
        .Select(x => new Item(x.First().Name, x.Key))
        .Select(withCalculatedValue);

或者简单地使用对象初始化器

return items.GroupBy(i => i.Value)
        .Select(x => new Item(x.First().Name, x.Key) { CalculatedValue = x.First().Name + x.Key });

如果您想对包含计算的对象这一主题做更多研究,google 术语 "Monad",但要做好被混淆的准备。

只是为了补充 dcastro 的好答案,即对 for 循环中的项目所做的更改(突变)发生在 first 迭代中 groupedItems,它们 没有被任何东西存储 ,并且如果 ChangeValueIEnumerable 的调用者迭代返回的 IEnumerable,原始的 groupedItems 将被执行第二次。

请注意,通过在 Select:

中进行投影,您可以在不使用 ToList() 的情况下避免此问题
var groupedItems = items.GroupBy(i => i.Value)
    .Select(x => new Item(x.First().Name, x.Key)
    {
        CalculatedValue = x.First().Name + x.Key
    });

return groupedItems;