Why I Don’t Use the ForEach Extension Method
In my first version of my functional programming tutorial, I discussed a ForEach extension method, but I removed the topic from the second version. This extension method iterates through a source collection and performs an action on each item in the collection. ForEach isn’t included in the .NET base class library (BCL), but its implementation is pretty simple:
This blog is inactive.
New blog: EricWhite.com/blog
static class MyExtensions
{
static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (T item in source)
action(item);
}
}
Perhaps the most common use of this extension method is to send output to the console:
static void Main(string[] args)
{
List<string> s = new List<string>() {
"abc",
"def",
"ghi"
};
s.ForEach(z => Console.WriteLine(z));
}
This extension method has been discussed in various blog posts. As my experience has grown in using LINQ and functional programming techniques, I’ve moved completely away from using it. This post explains why.
The common complaint about the ForEach extension method is that it encourages state mutation, which is contrary to the whole idea of stateless transformations. I’d go a step beyond, and say that the only possible use of the ForEach extension method is to perform actions that have side effects.
In pure functional programming, even simple outputting to the console is considered to be a side effect, the reason being that the compiler could in theory optimize your queries and perhaps parcel out portions of the query to different processors, and your nicely formatted report would become a jumbled mess. Currently, the C# compiler is deterministic – if you understand the laziness characteristics of your queries you can predict the order of execution. This possibly could change in the future, but this is really beside the point.
The reason to not use ForEach is that it blurs the boundary between pure functional code and state-full imperative code.
Over the last couple of years, I’ve used functional programming techniques in a variety of applications, including SharePoint features, ASP.NET applications, and Windows Forms applications. These frameworks are decidedly imperative in nature. However, I find it perfectly natural to combine pure functional code with traditional imperative code. But when I do so, I divide my code up so that the functional code is pure. If writing code for myself, I don’t bother to add comments in the code delineating the functional code from imperative code, but if writing code that others will maintain, I certainly comment on it. If I were to write production code that will be included in a product, I might separate the pure functional transformations into their own module, depending on the extent of the transformations.
In practice, I often write transforms to produce XML trees using LINQ to XML, even if my scenario doesn’t require XML. For example, when I wrote the code to automate testing of code that is embedded in documents, the pure functional transformation produces an XML tree that contains the ‘report’ of whether each unit test passed or failed. Then, code that is completely separate from the pure functional transform prints the report to the console. If I implemented that testing framework as a Windows Form application, I just as easily could have set text in controls in a form, or perhaps rows in a grid.
Alternatively, I write transformations that produce collections of strings, or collections of objects that have properties. In any case, I don’t combine pure functional code with imperative code within a single statement. I categorize outputting to the console as imperative code with side effects.
It could be possible to write an extension method that had the specific purpose of writing to the console, and that wouldn’t have such an ambiguous purpose as ForEach. However, it is easy instead to produce a collection of strings or whatever, completely separating the code that prints to the console. This separation gives me a warm and fuzzy feeling. My personal opinion is that ForEach should not be included in the BCL.