简体   繁体   English

如何迭代集合并使用LINQ扩展方法更改值?

[英]How can I iterate over a collection and change values with LINQ extension methods?

Lets say I have a collection of Messages which has the properties "UserID" (int) and "Unread" (bool). 假设我有一组消息,其中包含“UserID”(int)和“Unread”(bool)属性。

How can I use LINQ extension methods to set Unread = false, for any Message in the collection in whose UserID = 5? 对于UserID = 5的集合中的任何Message,如何使用LINQ扩展方法设置Unread = false?

So, I know I can do something like: 所以,我知道我可以这样做:

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

But, how do I set the Unread property of each of those with an extension method as well? 但是,如何使用扩展方法设置每个的Unread属性呢?

Note: I know I should not do this in production code. 注意:我知道我不应该在生产代码中这样做。 I'm simply trying to learn some more LINQ-fu. 我只是想学习更多LINQ-fu。

Actually, this is possible using only the built-in LINQ extension methods without ToList . 实际上,这可以仅使用没有ToList的内置LINQ扩展方法。
I believe that this will perform very similarly to a regular for loop. 我相信这将与常规for循环非常相似。 (I haven't checked) (我还没检查过)

Don't you dare do this in real code. 不敢在实际代码中这样做。

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

As an added bonus, this will return the number of users that it modified. 作为额外的奖励,这将返回其修改的用户数量。

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

然后提交更改。

Standard LINQ extension methods doesn't include side effects aimed methods. 标准LINQ扩展方法不包括针对方法的副作用。 However you can either implement it yourself or use from Reactive Extensions for .NET (Rx) like this: 但是,您可以自己实现它,也可以使用Reactive Extensions for .NET(Rx),如下所示:

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

As there is no explicit extension method that does a ForEach , you are stuck with either using a secondary library, or writing the foreach statement on your own. 由于没有明确的扩展方法可以执行ForEach ,因此您无论是使用辅助库还是自己编写foreach语句。

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

If you really want to use a Linq statement to accomplish this, create a copy the collection using the ToList() method, accessing the ForEach() method of the List type: 如果您确实想使用Linq语句来完成此操作,请使用ToList()方法创建集合的副本,访问List类型的ForEach()方法:

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

or place the side-effect in a Where() statement: 或将副作用放在Where()语句中:

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

In either case, I prefer to use the explicit foreach loop as it doesn't make unnecessary copies and is clearer than the Where hack. 在任何一种情况下,我更喜欢使用显式的foreach循环,因为它不会产生不必要的副本,并且比Where hack更清晰。

With LINQ you can't because LINQ is a query language/extension. 使用LINQ你不能,因为LINQ是一种查询语言/扩展。 There is however a project called MoreLinq , which defines an extension method called ForEach which allows you to pass an action which will be performed on every element. 然而,有一个名为MoreLinq的项目,它定义了一个名为ForEach的扩展方法,它允许您传递将在每个元素上执行的操作。

So, you could do with MoreLinq: 所以,你可以使用MoreLinq:

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

Best Regards, 最好的祝福,
Oliver Hanappi 奥利弗哈纳皮

This answer is in the spirit of providing a solution. 这个答案是本着提供解决方案的精神 On could create an extension which does both the predicate ( Where extension) to weed out the items and the action needed upon those items. On可以创建一个扩展,它既可以执行谓词( Where扩展),也可以执行这些项目所需的操作。

Below is an extension named OperateOn which is quite easy to write: 下面是一个名为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;
        });

    }
}

Here is it in action: 这是它的实际应用:

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

You should be able to just do it in a Select(), remember the lambda is a shortcut for a function, so you can put as much logic in there as you want, then return the current item being enumerated. 您应该能够在Select()中执行此操作,记住lambda是函数的快捷方式,因此您可以根据需要放置尽可能多的逻辑,然后返回枚举的当前项。 And... why exactly wouldn't you do this in production code? 并且......为什么你不能在生产代码中这样做?

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM