【问题标题】:Why do changes made in foreach to a Linq grouping select get ignored unless I add ToList()?为什么除非我添加 ToList(),否则在 foreach 中对 Linq 分组选择所做的更改会被忽略?
【发布时间】:2015-04-01 13:35:59
【问题描述】:

我有以下方法。

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 集合中,CalculatedValues 为空。但是,如果我在GroupBy 之后的Select 句子中添加ToList(),则CalculatedValues 具有值。 例如:

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

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

更新:Item 类的定义如下

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

【问题讨论】:

  • 你能发布Item类的定义吗?
  • @Enigmativity 我已经添加了 Item 类。谢谢
  • 顺便说一句,按照惯例,由于Item 接受构造函数参数,您应该考虑将关联属性NameValue 标记为private set,或者甚至更好,使用readonly支持字段来表达不变性。

标签: c# linq linq-group


【解决方案1】:
var groupedItems = items.GroupBy(i => i.Value)
    .Select(x => new Item(x.First().Name, x.Key));

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

每次迭代 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 中的每个项目打印两次“正在创建新项目”。

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

通过调用ToList,您将“计算”转化为实际的项目集合。

除了调用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”一词,但要做好混淆的准备。

【讨论】:

    【解决方案2】:

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

    请注意,您可以在不使用ToList() 的情况下通过在Select 中进行投影来避免该问题:

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

    【讨论】:

    • 我在您发布答案的同时修改了我的答案 :) +1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-08
    • 1970-01-01
    • 2017-11-02
    • 1970-01-01
    • 2015-09-07
    • 2011-10-05
    • 1970-01-01
    相关资源
    最近更新 更多