Why no ForEach method on IEnumerable interfaces
I was asked many times recently regarding this topic, why not Microsoft introduce an extension method ForEach() on interface IEnumerable<T> ? The answer is: "No, and never".
Here are some strong reasons to not bring this extension method on IEnumerable. I will be discussing the following design considerations in this post.
- Design pattern
- Runtime behavior
As we know, the C# built-in keyword foreach is a syntactical sugar. While compiling the source code contains foreach statements, the complier searches for the underlying object hierarchal inheritance tree to determine whether this object implements IEnumerable interface, if so, complier transforms foreach statement to call appropriate IEnumerable methods to form the loop (i.e. GetEnumerator, MoveNext etc.) That's to say, foreach keyword is based on the IEnumerable interface design pattern. Additionally, IEnumerable<T> is the generic form of IEnumerable interface (it implements IEnumerable), which improves the runtime type resolving performance by using generic type system, IEnumerable<T> is also the pattern to make foreach keyword work. Imagine adding a method ForEach() to these interfaces, does it look strange?
If IEnumerable had ForEach() method, how can we know exactly when we consider foreach keyword or IEnumerable.ForEach() ? Though whichever you take to lead the same actual result but the problem is still obvious – that is, keep the consistency of your code. I also remember there is also a discussion for where to consider using FCL types (e.g. System.String) or language built-in type aliases (e.g. string in C#). This kind of argue never ends, and standardize usages of these kinds of stuff is really very difficult.
You may think there is almost no difference if you would add an extension method ForEach to IEnumerable then use this call instead of foreach keyword, however it’s much tricky than you think. ForEach() may have been defined like this:
This method takes a parameter that is of type Action<T> , and Action<T> can be a lambda expression; each lambda expression can be converted either to an anonymous delegate, or an expression tree, according to the current calling execution context, it’s possible that the parameter of type Action<T> is translated to specific normal code blocks (which executes immediately) or even the “meaning” of the expression represented by the parameter (which not executes immediately). Specifically, when you had a ForEach() extension method defined on an IEnumerable<T> interface and apply this call on the IQueryable<T> instance or LINQ to SQL object, the ForEach() method will not actually be executed immediately, instead, the meaning of this call will be translated into special code recognized by the attached LINQ Provider on this type; as a result, you may get unexpected outputs. Furthermore, if you had defined ForEach() method on the base type for all LINQ enabled objects (i.e. IEnumerable<T> ). it may override the behavior of the same method on the derived types. For more information, see this blog post: http://ppetrov.wordpress.com/2009/01/22/foreach-method-on-ienumerable/
You have to aware that if you're using the foreach built-in keyword, the following cases with foreach statements will be optimized by C# compiler when compile the source code into IL.
- String: foreach (char item in myString) will directly use myString.Length instead of call myString.GetEnumerator().
- Array: foreach (var item in myArray) will directly use myArray.Length instead of call myArray.GetEnumerator().
- LINQ-enabled objects: foreach will execute the LINQ query immediately in a LINQ-enabled context (deferred execution).
- foreach (int item in GetItems()) – the method GetItems() will only be evaluated once.
Consider foreach keyword when possible. The built-in foreach keyword brings different runtime behaviors and compiler optimizations for the enumerable objects. Do not extend IEnumerable with defining a ForEach() extension method – it is dangerous, unverifiable, yet not a good design.
The answer from C# team is here: http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx. thanks Dixin Yan to provide this link.