VSInsider

Discretionary Development at Microsoft

Have you ever wondered why the Microsoft Base Class Library (BCL) lacks an IEnumerable<T>.ForEach(...) method? Obviously, Microsoft must be careful when extending any API.

The decision to add a method like IEnumerable<T>.ForEach(...) comes down to five things: demand, precedent, advantages, consistency and cost-effectiveness. Let's take a look at each.

The first question is, is there explicit customer demand for a new feature? In the case of IEnumerable<T>.ForEach(...), the answer is yes. The Microsoft Connect bug and feature inquiry site shows plenty of requests for this method, and Microsoft has begun investigating the value.

Another factor is precedent. Does the method exist elsewhere and have proven value? There's already a static implementation of ForEach() on System.Collections. Array and an instance method on System.Coll-e-ctions.Generic.List<T>. And ForEach() is in the Parallel LINQ (PLINQ) API.

Things get murky when you start talking about explicit advantages. IEnumerable<T>.ForEach(...) offers minimal advantage over a foreach statement. Compare the two examples here; one with foreach and one with IEnumerable<T>.ForEach(...), where items is an IEnumerable<string>:

  items.ForEach(item=> 
   {Console.WriteLine(item);});
  foreach (string item in items) 
    { Console.Write(item); }

Yes, if you use a lambda expression (items.ForEach(Console.WriteLine)) rather than a lambda statement, the syntax is more succinct, and still readable. But for a statement block with multiple statements, it's likely that most developers would prefer the foreach statement.

The next issue is consistency, and here the outlook is less clear. Typically, the exception methods provided by System.Linq.Enumerable return a collection of objects -- generally IEnumerable<T>. This is critical because it enables deferred execution, delaying query execution until an explicit call for a result is needed. In the following example, none of the lambda expressions is evaluated until the call to Count():

  items = items.Where(
   item => item == item.ToString());
  items = items.Where(
    item => item.Length > 0);
  items = items.OrderBy(item=>item);
  IEnumerable<char> firstLetters = 
    items.Select(item => item[0]);
  int count = 
    firstLetters.Distinct().Count();

Until the call to Count(), the criteria expressed by each lambda expression is combined together into one big query, rather than executed (or evaluated) piecemeal.

Although having IEnumerable<T>.ForEach(...) return an IEnumerable<T> collection would be consistent with the earlier, well-established pattern, it would also likely lead to runtime coding errors because statements like items.ForEach(Console.WriteLine) wouldn't write anything out. Rather, the execution of the WriteLine statement would be delayed until the expression was evaluated. Unless the result of ForEach() was assigned, it would never be evaluated, making for a line of code that appeared to be exactly what was needed but, in actuality, did nothing.

In conclusion, it's likely that consistency with other System.Linq.Enumerable methods should probably be dropped, as it was for List<T>.ForEach(). That means losing support for having a fluent API and its characteristic method chaining.

Last comes the question of cost. Implementing IEnumerable<T>.ForEach(...) is fairly trivial, as shown here:

  public static void ForEach<T>(
   this IEnumerable<T> collection, 
   Action<T> action)
 {
   foreach (T item in collection)
   {
     action(item);
   }
 }

However, without testing, it's likely that the consequence of deferred execution would go unnoticed. The method is small and clear enough that many would naively assume they could write it correctly without unit testing it. And, even when returning IEnumerable<T>, they would. However, once testing was in place to demonstrate that deferred execution would yield very misleading code, it would be discovered that the design needed more thought.

Also, because the method is easy to implement, it is perhaps not as crucial to include in the BCL -- a developer could implement it themselves.

IEnumerable<T>.ForEach(...) is an example of a relatively trivial API with several factors in its favor. But Microsoft must evaluate the issues very closely. We should pay similar attention to our own API design and the code we write. Will the company provide IEnumerable<T>.ForEach(...) in the Microsoft .NET Framework 5? No doubt Microsoft is considering it.

About the Author

Mark Michaelis (http://IntelliTect.com/Mark) is the founder of IntelliTect and serves as the Chief Technical Architect and Trainer. Since 1996, he has been a Microsoft MVP for C#, Visual Studio Team System, and the Windows SDK and in 2007 he was recognized as a Microsoft Regional Director. He also serves on several Microsoft software design review teams, including C#, the Connected Systems Division, and VSTS. Mark speaks at developer conferences and has written numerous articles and books - Essential C# 5.0 is his most recent. Mark holds a Bachelor of Arts in Philosophy from the University of Illinois and a Masters in Computer Science from the Illinois Institute of Technology. When not bonding with his computer, Mark is busy with his family or training for another triathlon (having completed the Ironman in 2008). Mark lives in Spokane, Washington, with his wife Elisabeth and three children, Benjamin, Hanna and Abigail.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube