LINQ Walk[source]
xml
<glacius:metadata> | |
<title>LINQ Walk</title> | |
<description>Implementation of a walk() extension method for LINQ</description> | |
<category>Legacy blog posts</category> | |
<category>Programming</category> | |
<category>C#</category> | |
</glacius:metadata> | |
<glacius:macro name="legacy blargh banner"> | |
<properties> | |
<originalUrl>https://tmont.com/blargh/2010/3/linq-walk</originalUrl> | |
<originalDate>2010-03-24T08:05:57.000Z</originalDate> | |
</properties> | |
</glacius:macro> | |
<p> | |
So, we all know that | |
<a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/">LINQ</a> | |
is rad. Not in that silly faux-SQL syntax, but in the declarative, fluent syntax. | |
</p> | |
<p> | |
But the sad part is that there is no <code>walk</code> extension method. Walk is a | |
functional programming staple that iterates over a collection of stuff and applies a | |
callback to each item. | |
</p> | |
<p> | |
You might be thinking, "but there is a <code>ForEach()</code> extension method on | |
<code>List</code>!". And you would be wrong, because there is a subtle, yet important | |
difference between <code>IEnumerable</code> and <code>IList</code>. | |
</p> | |
<p> | |
<code>IEnumerable</code> 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 | |
<code>foreach</code> construct. In a list, there is no deferred execution. This deferred | |
execution is accomplished through the magic of closures and expression trees. | |
</p> | |
<p>In the meantime, here is a fluent implementation of <code>walk</code>:</p> | |
<glacius:code lang="csharp"><![CDATA[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; | |
} | |
} | |
}]]></glacius:code> | |
<p>You can prove that this is in fact using deferred execution with a simple test:</p> | |
<glacius:code lang="csharp"><![CDATA[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(); | |
} | |
}]]></glacius:code> | |
<p>The output looks like this:</p> | |
<p class="text-center"> | |
<img glacius:src="linqwalk1.png" alt="LINQ walk output" /> | |
</p> | |
<p> | |
Notice that nothing got printed the first time through, using the <code>Walk()</code> 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: | |
</p> | |
<glacius:code lang="csharp"><![CDATA[ | |
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();]]></glacius:code> | |
<p class="text-center"> | |
<img glacius:src="linqwalk2.png" alt="LINQ walk output 2" /> | |
</p> | |
<p>Well, maybe that's not "normal." Whatever.</p> |