Among many useful LINQ methods, there's no functional ForEach. People often implement it on their own, an extension method is just a few lines of code.
Someone did that in one of our projects, too:
public static void ForEach1<T>( this IEnumerable<T> enumeration, Action<T> action ) { foreach ( T item in enumeration ) { action( item ); } }
This works great. Someone else, however, noticed that while this works great, it doesn't allow further chaining, since the method returns void
This someone else modified the code slightly:
public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action ) { foreach ( T item in enumeration ) { action( item ); yield return item; } }
This doesn't work great. Frankly, it doesn't work at all. Surprising but true. Take this:
internal class Program { static void Main( string[] args ) { Test1(); Test2(); Console.ReadLine(); } static void Test1() { var array = new int[] { 1,2,3 }; var list = new List<int>(); array.ForEach1( e => list.Add( e ) ); Console.WriteLine( list.Count() ); } static void Test2() { var array = new int[] { 1,2,3 }; var list = new List<int>(); array.ForEach2( e => list.Add( e ) ); Console.WriteLine( list.Count() ); } } public static class EnumerableExtensions { public static void ForEach1<T>( this IEnumerable<T> enumeration, Action<T> action ) { foreach ( T item in enumeration ) { action( item ); } } public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action ) { foreach ( T item in enumeration ) { action( item ); yield return item; } } }
While this is expected to produce 3 for both tests, it produces 3 and 0. The upgraded version doesn't work as expected.
The reason for this is a kind-of-a-limitation of the state machine generated by the yield sugar. The machine doesn't execute any code until the very result is enumerated. This means that changing
array.ForEach2( e => list.Add( e ) );
to
array.ForEach2( e => list.Add( e ) ).ToList();
would "fix it". What a poor foreach, though, that requires an extra terminator (the ToList) and doesn't work otherwise.
Luckily, a simpler fix exists, just forget the state machine at all for this specific case:
public static IEnumerable<T> ForEach2<T>( this IEnumerable<T> enumeration, Action<T> action ) { foreach ( T item in enumeration ) { action( item ); } return enumeration; }
No comments:
Post a Comment