LINQ Walk
This article was originally published in my blog (affectionately referred to as blargh) on . The original blog no longer exists as I've migrated everything to this wiki.
The original URL of this post was at https://tmont.com/blargh/2010/3/linq-walk. Hopefully that link redirects back to this page.
So, we all know that LINQ is rad. Not in that silly faux-SQL syntax, but in the declarative, fluent syntax.
But the sad part is that there is no walk
extension method. Walk is a
functional programming staple that iterates over a collection of stuff and applies a
callback to each item.
You might be thinking, "but there is a ForEach()
extension method on
List
!". And you would be wrong, because there is a subtle, yet important
difference between IEnumerable
and IList
.
IEnumerable
is late-binding. That means you can do whatever you want to
it, but execution is deferred until you actually enumerate the enumeration, i.e. using a
foreach
construct. In a list, there is no deferred execution. This deferred
execution is accomplished through the magic of closures and expression trees.
In the meantime, here is a fluent implementation of walk
:
public static class LinqExtensions {
public static IEnumerable<T> Walk<T>(this IEnumerable<T> source, Action<T> action) {
foreach (var t in source) {
action(t);
yield return t;
}
}
}
You can prove that this is in fact using deferred execution with a simple test:
class Program {
class Foo {
public int Bar { get; set; }
public override string ToString() {
return string.Format("Foo(Bar={0})", Bar);
}
}
static void Main() {
IEnumerable<Foo> foos = new[] { new Foo { Bar = 2 }, new Foo { Bar = 1 }, new Foo { Bar = 3 } };
Console.WriteLine("IEnumerable.Walk():");
foos.Walk(Console.WriteLine);
Console.WriteLine();
Console.WriteLine("List.ForEach():");
foos.ToList().ForEach(Console.WriteLine);
Console.WriteLine();
Console.ReadLine();
}
}
The output looks like this:
Notice that nothing got printed the first time through, using the Walk()
extension
method. There's your deferred execution. That means our extension method did not enumerate the
enumeration, so it's safe and efficient to use walk in a normal linq expression where you are
depending on deferred execution, like this:
IEnumerable<Foo> foos = new[] { new Foo { Bar = 2 }, new Foo { Bar = 1 }, new Foo { Bar = 3 } };
Console.WriteLine("Doing more stuff:");
var listOStuff = foos
.Where(foo => foo.Bar >= 2)
.Walk(foo => foo.Bar += 10)
.Select(foo => new Baz { Foofy = foo })
.Walk(baz => baz.Foofy.Bar--)
.OrderBy(baz =>; baz.Foofy.Bar);
foreach (var stuff in listOStuff) {
Console.WriteLine(stuff);
}
Console.ReadLine();
Well, maybe that's not "normal." Whatever.