A while back I played with Ruby-style loops in C# 3.0. This sparked my jealousy of other fun Ruby constructs that I couldn't find in C#, and a couple of them are the "each" and "each_with_index" methods for arrays. Here's an example, from thinkvitamin.com:
my_vitamins = ['b-12', 'c', 'riboflavin'] my_vitamins.each do |vitamin| puts "#{vitamin} is tasty!" end => b-12 is tasty! => c is tasty! => riboflavin is tasty!
With both Arrays and List<T> in .NET, this is already possible:
string[] myVitamins = {"b-12", "c", "riboflavin"}; Array.ForEach(myVitamins, (vitamin) => { Console.WriteLine("{0} is tasty", vitamin); } ); var myOtherVitamins = new List<string>() { "b-12", "c", "riboflavin" }; myOtherVitamins.ForEach( (vitamin) => { Console.WriteLine("{0} is very tasty", vitamin); } );
There are a few problems with these implementations, however:
- Inconsistent between types
- IEnumerable<T> left out
- Array has a static method, whereas List<T> is instance
- Index is unknown
Since T[] implicitly implements IEnumerable<T>, we can create a simple extension method to handle any case.
Without index
I still like the "Do" keyword in Ruby to signify the start of a block, and I'm not a fan of the readability (or "solubility", whatever) of the "ForEach" method. Instead, I'll borrow from the loop-style syntax I created in the previous post that uses a "Do" method:
myVitamins.Each().Do(
(vitamin) =>
{
Console.WriteLine("{0} is tasty", vitamin);
}
);
To accomplish this, I'll need something to add the "Each" method, and something to provide the "Do" method. Here's what I came up with:
public static class RubyArrayExtensions { public class EachIterator<T> { private readonly IEnumerable<T> values; internal EachIterator(IEnumerable<T> values) { this.values = values; } public void Do(Action<T> action) { foreach (var item in values) { action(item); } } } public static EachIterator<T> Each<T>(this IEnumerable<T> values) { return new EachIterator<T>(values); } }
The "Each" generic method is an extension method that extends anything that implements IEnumerable<T>, which includes arrays, List<T>, and many others. IEnumerable<T> is ripe for extension, as .NET 3.5 introduced dozens of extension methods for it in the System.Linq.Enumerable class. With these changes, I now have a consistent mechanism to perform an action against an array or list of items:
string[] myVitamins = { "b-12", "c", "riboflavin" }; myVitamins.Each().Do( (vitamin) => { Console.WriteLine("{0} is tasty", vitamin); } ); var myOtherVitamins = new List<string>() { "b-12", "c", "riboflavin" }; myOtherVitamins.Each().Do( (vitamin) => { Console.WriteLine("{0} is very tasty", vitamin); } );
With index
Ruby also has a "each_with_index" method for arrays, and in this case, there aren't any existing methods on System.Array or List<T> to accomplish this. With extension methods, this is still trivial to accomplish. I now just include the index whenever executing the callback to the Action<T, int> passed in. Here's the extension method with the index:
public static class RubyArrayExtensions { public class EachWithIndexIterator<T> { private readonly IEnumerable<T> values; internal EachWithIndexIterator(IEnumerable<T> values) { this.values = values; } public void Do(Action<T, int> action) { int i = 0; foreach (var item in values) { action(item, i++); } } } public static EachWithIndexIterator<T> EachWithIndex<T>(this IEnumerable<T> values) { return new EachWithIndexIterator<T>(values); } }
The only difference here is I keep track of an index to send back to the delegate passed in from the client side, which now looks like this:
string[] myVitamins = { "b-12", "c", "riboflavin" }; myVitamins.EachWithIndex().Do( (vitamin, index) => { Console.WriteLine("{0} cheers for {1}!", index, vitamin); } ); var myOtherVitamins = new List<string>() { "b-12", "c", "riboflavin" }; myOtherVitamins.EachWithIndex().Do( (vitamin, index) => { Console.WriteLine("{0} cheers for {1}!", index, vitamin); } );
This now outputs:
0 cheers for b-12! 1 cheers for c! 2 cheers for riboflavin! 0 cheers for b-12! 1 cheers for c! 2 cheers for riboflavin!
Pointless but fun
I don't think I'd ever introduce these into production code, as it's never fun to drop new ways to loop on other's laps. If anything, it shows how even parentheses can hinder readability, even if the method names themselves read better.
In any case, I now have a simple, unified mechanism to perform an action against any type that implements IEnumerable<T>, which includes arrays and List<T>.
No comments:
Post a Comment