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:

C♯
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:

C♯
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:

LINQ walk output

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:

C♯
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();

LINQ walk output 2

Well, maybe that's not "normal." Whatever.