【问题标题】:How can I iterate over a collection and change values with LINQ extension methods?如何使用 LINQ 扩展方法迭代集合并更改值?
【发布时间】:2009-12-30 16:35:42
【问题描述】:

假设我有一个消息集合,其中包含属性“UserID”(int)和“Unread”(bool)。

对于集合中 UserID = 5 的任何消息,我如何使用 LINQ 扩展方法设置 Unread = false?

所以,我知道我可以这样做:

messages.Any(m => m.UserID == 5);

但是,我如何设置每个具有扩展方法的未读属性?

注意:我知道我不应该在生产代码中这样做。我只是想学习更多 LINQ-fu。

【问题讨论】:

  • 有什么理由不能使用常规的 for-each 迭代过滤的集合吗?
  • @helios,没有。这不是生产代码。我只是玩得很​​开心,我很好奇是否可以使用 LINQ 迭代这些项目
  • 只是为了约定和一般的理智,称之为已读,而不是未读。 if(m.Read) 比 if (!m.Unread) 更容易理解。

标签: c# .net linq linq-to-objects


【解决方案1】:

实际上,仅使用内置的 LINQ 扩展方法而不使用 ToList 是可能的。
我相信这将与常规的for 循环非常相似。 (我没查过)

难道你在实际代码中这样做吗?

messages.Where(m => m.UserID == 5)
        .Aggregate(0, (m, r) => { m.Unread = false; return r + 1; });

作为额外的奖励,这将返回它修改的用户数量。

【讨论】:

  • 您能解释一下为什么我们不应该在生产代码中这样做吗?
【解决方案2】:

messages.Where(m => m.UserID == 5).ToList().ForEach(m => m.Unread = false);

然后提交更改。

【讨论】:

  • Any 返回一个布尔值,所以这是不正确的——你可能想要Where
  • Any 不正确,因为它检查集合中是否至少有一条 UserID 为 5 的消息并返回 true 或 false。您需要使用 Where 而不是 any。另请注意,当集合非常大时,创建列表可能会很昂贵。另请参阅我的回答,其中建议使用 MoreLinq 的扩展方法 ForEach。
  • 选择不正确 - 这将返回一个 IEnumerable<bool>。你的意思是Where
  • 是的,很抱歉,只是还没醒。现在修好了。
【解决方案3】:

标准 LINQ 扩展方法不包括针对副作用的方法。但是,您可以自己实现它,也可以像这样使用Reactive Extensions for .NET (Rx)

messages.Where(m => m.UserID == 5).Run(m => m.Unread = false);

【讨论】:

    【解决方案4】:

    由于没有明确的扩展方法可以执行 ForEach,因此您要么使用辅助库,要么自己编写 foreach 语句。

    foreach (Message msg in messages.Where(m => m.UserID == 5))
    {
        msg.Unread = false;
    }
    

    如果您确实想使用 Linq 语句来完成此操作,请使用 ToList() 方法创建一个集合副本,访问 List 类型的 ForEach() 方法:

    messages.Where(m => m.UserID == 5).ToList().ForEach(m => m.Unread = false);
    

    或将副作用放在Where() 语句中:

    messages.Where(m =>
    {
        if (m.UserID == 5) { m.Unread = false; return true; }
        return false;
    });
    

    在任何一种情况下,我都更喜欢使用显式的foreach 循环,因为它不会产生不必要的复制,并且比Where hack 更清晰。

    【讨论】:

    • 调用Select 在枚举结果之前实际上不会做任何事情。您应该在Select 的返回值上调用.Count() 以强制枚举整个集合。此外,您的第一个 Select 实际上应该是 Where
    • 在发帖后立即发现。
    • 最后一种方法还是不行。在枚举其返回值之前,调用Where 实际上不会做任何事情。您需要致电.Count()。另外,你错过了一个右括号。
    【解决方案5】:

    您不能使用 LINQ,因为 LINQ 是一种查询语言/扩展。但是有一个名为 MoreLinq 的项目,它定义了一个名为 ForEach 的扩展方法,它允许您传递将在每个元素上执行的操作。

    所以,您可以使用 MoreLinq:

    messages.Where(m => m.UserID == 5).ForEach(m => m.Unread = false);
    

    最好的问候,
    奥利弗·哈纳皮

    【讨论】:

    • 严格来说这不是真的。 LINQ 无法做到这一点的原因与它是一种查询语言/扩展这一事实无关。 (除了 LINQ 可以做到这一点)
    【解决方案6】:

    这个答案本着提供解决方案的精神。 On 可以创建一个扩展,它同时执行谓词(Where 扩展)以清除项目和对这些项目所需的操作。

    下面是一个名为OperateOn的扩展,写起来很简单:

    public static void OperateOn<TSource>(this List<TSource> items, 
                                          Func<TSource, bool> predicate, 
                                          Action<TSource> operation)
    {
        if ((items != null) && (items.Any()))
        {
            items.All (itm =>
            {
                if (predicate(itm))
                    operation(itm);
    
                return true;
            });
    
        }
    }
    

    下面是实际操作:

    var myList = new List<Item>
                       { new Item() { UserId = 5, Name = "Alpha" },
                         new Item() { UserId = 5, Name = "Beta", UnRead = true },
                         new Item() { UserId = 6, Name = "Gamma", UnRead = false }
                       };
    
    
    myList.OperateOn(itm => itm.UserId == 5, itm => itm.UnRead = true);
    
    Console.WriteLine (string.Join(" ",
                                   myList.Select (itm => string.Format("({0} : {1})",
                                                                       itm.Name,
                                                                       itm.UnRead ))));
    
    /* Outputs this to the screen
    
    (Alpha : True) (Beta : True) (Gamma : False)
    
    */
    

    ...

    public class Item
    {
        public bool UnRead { get; set; }
        public int UserId { get; set; }
        public string Name { get; set; }
    }
    

    【讨论】:

      【解决方案7】:

      您应该可以在 Select() 中执行此操作,记住 lambda 是函数的快捷方式,因此您可以在其中放置任意数量的逻辑,然后返回当前正在枚举的项目。而且...为什么您不在生产代码中执行此操作?

      messages = messages
          .Select(m => 
          {
              if (m.UserId == 5) 
                  m.Unread = true;
              return m;
          });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-12-28
        • 2011-03-04
        • 2018-10-25
        • 2019-12-09
        • 1970-01-01
        • 1970-01-01
        • 2011-04-04
        • 1970-01-01
        相关资源
        最近更新 更多