views:

194

answers:

5

Lets say I have a collection of Messages which has the properties "UserID" (int) and "Unread" (bool).

How can I use LINQ extension methods to set Unread = false, for any Message in the collection in whose UserID = 5?

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?

Note: I know I should not do this in production code. I'm simply trying to learn some more LINQ-fu.

+3  A: 

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

Then submit the changes.

David
`Any` returns a bool, so that's not correct- you'd probably want `Where`
Factor Mystic
Any is not correct because it checks if there is at least one message with UserID 5 in the collection and returns true or false. You need to use Where instead of any. Also be aware of the fact that creating a list can be expensive when the collection is really big. See also my answer which suggests MoreLinq's extension method ForEach.
Oliver Hanappi
Select isn't right - that's going to return an `IEnumerable<bool>`. You mean `Where`.
Jon Skeet
Yeah, sorry about that, just not awake yet. Fixed it now.
David
+2  A: 

Hi!

With LINQ you can't because LINQ is a query language/extension. 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.

So, you could do with MoreLinq:

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

Best Regards,
Oliver Hanappi

Oliver Hanappi
This is not strictly true. The reason that LINQ can't do this has nothing to do with the fact that it's a query language/extension. (Besides the fact that LINQ can do this)
SLaks
+4  A: 

Standard LINQ extension methods doesn't include side effects aimed methods. However you can either implement it yourself or use from Reactive Extensions for .NET (Rx) like this:

messages.Where(m => m.UserID == 5).Run(m => m.Unread = false);
Dzmitry Huba
+6  A: 

Actually, this is possible using only the built-in LINQ extension methods without ToList.
I believe that this will perform very similarly to a regular for loop. (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.

SLaks
Oh wow. That is pretty cool.
KingNestor
A: 

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 (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:

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

or place the side-effect in a Where() statement:

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.

Steve Guidi
Calling `Select` won't actually do anything until the result is enumerated. You should call `.Count()` on `Select`'s return value to force the entire collection to be enumerated. Also, your first `Select` should actually be `Where`.
SLaks
Caught that immediately after posting.
Steve Guidi
The last method still won't work. Calling `Where` won't actually do anything until its return value is enumerated. You need to call `.Count()`. Also, you missed a closing parenthesis.
SLaks