Well, I missed MVP Summit this year, so while fellow MVPs enjoying together in Redmond I'm playing with C# 3.0 at home. And I'm in the process of Ruby learning, so what I spotted immediately is the lack (correct me if I'm wrong) of Each() and Map() support in .NET 3.5 collections.
In Ruby you can apply a block of code to each element in a collection using very elegant each() method:
[1,2,3].each { |item|
puts item*item }
Each() method is basically a Visitor pattern implementation. I wonder why no such handy method exists in .NET 3.5? Dozens and dozens of new extension methods on collections covering every single aspect of collection manipulation from filtering to aggregation, but no basic functional programming facilities like Each() and Map()? Probably whoever at Microsoft decided foreach loop is still preferable solution for processing each element in a collection. That's what foreach does and does well.
What would be advantages of having Each() method instead of foreach loop?
- Syntactically foreach loop is a statement, while Each() method is expression. Expressions are usually simpler structurally than statements and more readable.
- foreach loop follows and encourages imperative programming style, Each() - functional one.
- Clean and beautiful
So I put together some quick implementation of IEnumerable.Each(subroutine) and IEnumerable.EachIndex(subroutine) extension methods after Ruby's
IEnumerable.Each(subroutine) extension method calls given subroutine (just a function returning void) once for each element in the collection, passing that element as a parameter.
IEnumerable.EachIndex(subroutine) extension method does the same, but but passes the index of the element instead of the element itself.
With those two methods I can process collections in Ruby-style functional code style:
int[] squares = new int[10]; squares.EachIndex(i => squares[i] = i * i); squares.Each(val => Console.WriteLine(val));
The implementation is suspiciously easy:
using System; using System.Collections.Generic; using System.Linq; namespace Test { public static class MyIEnumerableExtensions { public static void Each<TSource>(this IEnumerable<TSource> source, Action<TSource> action) { if (source == null) { throw new ArgumentNullException("source"); } if (action == null) { return; } foreach (TSource item in source) { action(item); } } public static void EachIndex<TSource>(this IEnumerable<TSource> source, Action<int> action) { if (source == null) { throw new ArgumentNullException("source"); } if (action == null) { return; } int i = 0; IEnumerator<TSource> enumerator = source.GetEnumerator(); while (enumerator.MoveNext()) { action(i++); } } } }Cool. Now what about transforming collection elements, I mean Map()?
Instead of:
squares.Each(val => Console.WriteLine(val));
Why not just do
foreach (var val in squares) Console.WriteLine(val);
Pretty simple, faster, and no funky extension methods to worry about (+ you dont have to write another using *** at the top of every class file). Guess its not as cool though ;)
Also, not to nitpick, but:
"""Each() method is basically a Visitor pattern implementation. """
Thinking about "Each" as any kind of pattern waaaaay over complicates it. Its just iteration over elements. :)
Thanks guys for mentioning Action delegate, I'm confirmed total ignoramus. I updated the code.
Scott, I think I have to declare generic parameter in method declaration - first AFAIK there is no non-generic Action version and then it would require working with object and so downcasting in lambda function. I'm not sure I'm following you though.
Btw, I omitted generic parameter in usage code - it could be written as
squares.EachIndex<int>(i => squares[i] = i * i);
but I wanted to be it as close to Ruby as it can be.
Mark, IndexedAction is also good idea, that matches Ruby's Enumerable.each_with_index() method.
(woking in .Neet 2.0)
The List class has a ForEach function :-
public void ForEach(Action action)
, which is not as flexible as your extension methods, but you may wish to utilise the Action delegate from the System namespace:-
public delegate void Action(T obj);
Having said that I then created another delegate:-
public delegate bool IndexedAction(T item, int position);
to pass the index and the object.
Hi Oleg
[1] Rather than define a new delegate why not simply use the existing Action?
[2] Surely you have to declare your generic parameter?
eg.
public static void Each(this IEnumerable source, Action sub), and
public static void EachIndex(this IEnumerable source, Action sub)
Nit-picking aside; that is a slick solution. :-)