views:

48

answers:

5

I have a class with two properties, say

public class Book {
public string TitleSource { get; set; }
public string TitleTarget { get; set; }
}

I have an IList<Book> where the TitleTarget is null and for each item in the list, I need to copy the TitleSource property to the TitleTarget property. I could do this through a loop, sure, but it seems like there's a LINQ or nice declarative way to do this. Is there?

+3  A: 

Linq was designed as a way to consume things. If you look at web discussions about why there is no IEnumerable.ForEach(...) extension, you'll see that the Linq designers purposefully avoided Linq to Object scenarios where the methods were designed to change object values.

That said, you can cheat by "selecting" values and not using the results. But, that creates items which are thrown away. So, a foreach loop is much more efficient.

Edit for people who really want something besides foreach

Another "cheat" that wouldn't produce a new list would be to use a method that does little work of it's own, like Aggregate, All, or Any.

// Return true so All will go through the whole list.
books.All(book => { book.TitleTarget = book.TitleSource; return true; });
John Fisher
+1  A: 

It's not LINQ as such, but there's:

books.Where(book => book.TitleTarget == null).ToList()
     .ForEach(book => book.TitleTarget = book.TitleSource);

The main point is the ToList method call: there's no ForEach extension method (I don't think?) but there is one on List<T> directly. It wouldn't be hard to write your own ForEach extension method as well.

As to whether this would be better than a simple foreach loop, I'm not so sure. I would personally choose the foreach loop, since it makes the intention (that you want to modify the collection) a bit clearer.

Dean Harding
The downside of this (besides extra typing) is that you create a list just to use a method you don't really need.
John Fisher
@John: that's right, which is why I said you could also write your own `IEnumerable<T>.ForEach` if you wanted (it's only a couple of lines, really). But anyway, I would probably just use a straight `foreach` loop, myself...
Dean Harding
.Where doesn't create a list. It creates an IEnumerable that's never fully materialized simultaneously.
recursive
@recursive, that's why you need to call ToList() or somehting else that creates objects - so the Where clause will actually do something.
John Fisher
@John Fisher: You only need to call .ToList() if you actually need a list. .ForEach works just as well on an Enumerator as it does on a List. My point is that no auxiliary list is constructed. The elements are loaded lazily as in a generator.
recursive
@recursive: there is no `ForEach` extension method for `IEnumerable<T>`. Instead, it's a method directly on `List<T>`. That's why you need to call `ToList()` to construct a `List<T>`. As I said, though, writing your own version of `ForEach` is not hard...
Dean Harding
My mistake. For some reason, I thought that was a linq method. I stand corrected.
recursive
+1  A: 

@John Fisher is correct, there is no IEnumerable.ForEach.

There is however a ForEach on List<T>. So you could do the following:

List<Book> books = GetBooks();
books.ForEach(b => b.TitleTarget = b.TitleSource);

If you wanted a IEnumerable.ForEach it would be easy to create one:

public static class LinqExtensions
{
    public static void ForEach<TSource>(this IEnumerable<TSource> source, Action<TSource> action)
    {
        foreach (var item in source)
        {
            action(item);
        }
    }
}

You can then use the following snippet to perform your action across your collection:

IList<Book> books = GetBooks();
books.ForEach(b => b.TitleTarget = b.TitleSource);
Wallace Breza
+1  A: 

If you can use .NET 4.0, and you are using a thread-safe collection then you can use the new parallel ForEach construct:

using System.Threading.Tasks;

...

Parallel.ForEach(
    books.Where(book => book.TitleTarget == null),
    book => book.TitleTarget = book.TitleSource);

This will queue tasks to be run on the thread pool - one task that will execute the assignment delegate for each book in the collection.

For large data sets this may give a performance boost, but for smaller sets may actually be slower, given the overhead of managing the thread synchronization.

Sam
+1 I like this, but only an option if you're on .NET 4 of course :)
Dean Harding
A: 
books.Select(b => b.TitleTarget = b.TitleSource);

This doesn't create any 'new items', just a query that you won't enumerate. That doesn't seem like a big deal to me.

Necros