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

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube