C# 2.0 comes packed with enough new syntactic sugar to keep your average developer on a high for a couple weeks. For those who wish to gain a good understanding of each of the newly added features, I highly recommend reading this. There’s no substitute for a well written technology specification.
I was playing with some combinations of these new constructs today on a flight, and the power really became apparent when I realized I was one step closer to Ruby-like concepts, yet still living in a relatively type-safe world.
The particular constructs I speak of are Generics, Anonymous Methods, and Iterators. For a great overview of each, refer to the document I mentioned above.
Taken from the Programming Ruby: The Pragmatic Programmer’s Guide, consider the following Ruby code to calculate a Fibonacci series:
def fibUpTo(max)
i1, i2 = 1, 1 # parallel assignment
while i1 <= max
yield i1
i1, i2 = i2, i1+i2
end
end
fibUpTo(1000) { |f| print f, " " }
To accomplish something along these lines in C# 1.0, one would have to either insert the closure action (print f, " " in this case) right in the fibUpTo method:
void fibUpTo(int max)
{
int i1 = 1;
int i2 = 1;
while (i1 < max)
{
Console.Out.WriteLine(“{0} “, i1);
int t = i1;
i1 = i2;
i2 = t + i2;
}
}
// ...
fibUpTo(1000);
// ...
Or, one could pass a delegate and create a method to execute this action.
void fibUpTo(int max, fibAction f)
{
int i1 = 1;
int i2 = 1;
while (i1 < max)
{
f(i1);
int t = i1;
i1 = i2;
i2 = t + i2;
}
}
delegate void fibAction(int n);
void myFibAction(int n)
{
Console.Out.WriteLine(“{0} “, n);
}
// ...
fibUpTo(1000, new fibAction(this.myFibAction));
// ...
The first approach has its obvious drawbacks of not allowing easy replacement of the iteration action without modifying the algorithm itself, while the second unnecessarily adds a callable unit of code where we might only want to use it for this particular case. With C# 2.0's anonymous methods, however, I can get even closer to the Ruby syntax:
void fibUpTo(int max, fibAction f)
{
int i1 = 1;
int i2 = 1;
while (i1 < max)
{
f(i1);
int t = i1;
i1 = i2;
i2 = t + i2;
}
}
delegate void fibAction(int n);
// ...
fibUpTo(1000, delegate(int n) {
Console.Out.WriteLine(“{0} “, n);
});
// ...
I like this quite a bit. Everything is where I would expect it to be. The algorithm is nice and clean, and the action to be executed is right inline where it’s being used. I could also use an iterator approach, reducing my lines of code, as follows:
IEnumerable fibUpTo(int max)
{
int i1 = 1;
int i2 = 1;
while (i1 < max)
{
yield return i1;
int t = i1;
i1 = i2;
i2 = t + i2;
}
}
// ...
foreach (int n in fibUpTo(1000))
Console.Out.WriteLine(“{0} “, n);
// ...
I like this quite a bit more. No delegates to worry about. Nice and clean. It's also pretty neat when you consider that this is in effect an incremental calculation of the algorithm.
In addition to the above, I find the following example particularly intriguing. Consider the following Ruby code, which dynamically selects items from a collection based on a conditional:
[ 1, 3, 5, 7, 9 ].find_all { |v| v*v > 30 }.each { |v| puts v }
With the following C# 2.0 hackery, such prose because almost possible. First, we define a wrapper around a standard IEnumerable<> object:
delegate bool FindCallback<T>(T current);
class Finder<T>
{
public Finder(IEnumerable<T> e)
{
this.e = e;
}
IEnumerable<T> e;
public IEnumerable<T> Find(FindCallback<T> callback)
{
foreach (T t in e)
if (callback(t))
yield return t;
}
}
Now, we can do similar things to the above Ruby code. Wallah…
foreach (int v in new Finder(new int[] { 1, 3, 5, 7, 9 }).Find(delegate(int x) { return x*x > 30; }))
Console.Out.WriteLine(v);
But we can of course do better. Forget the iterator altogether, and simply provide two anonymous methods: one for the selection criteria, the other to be executed upon a match.
The wrapper changes to:
delegate bool FindCallback<T>(T current);
delegate void ExecuteCallback<T>(T current);
class Finder<T>
{
public Finder(IEnumerable<T> e)
{
this.e = e;
}
IEnumerable<T> e;
public void Find(FindCallback<T> callback, ExecuteCallback<T> exec)
{
foreach (T t in e)
if (callback(t))
exec(t);
}
}
And thus we have:
new Finder(Array.AsReadOnly(new int[] { 1, 3, 5, 7, 9 })).Find(delegate(int v) { return v*v > 30; }, delegate(int v) { Console.Out.WriteLine(v); });
This becomes a bit more legible when formatted more appropriately:
int[] numbers = new int[] { 1, 3, 5, 7, 9 };
Finder f = new Finder(Array.AsReadOnly(numbers));
f.Find(delegate(int v) { return v*v > 30; },
delegate(int v) { Console.Out.WriteLine(v); });
While this is starting to look a lot like passing around function pointers in C/C++, a number of more expressive and powerful iteration techniques become possible with the new syntax additions to C# 2.0. I’m looking forward to unleashing such cryptic script-isms into the wild. ;)
Very cool.
[Note: After writing all of this, I took a closer look at the System.Collections.Generic API. Doh! Most of this is already in there. Look specifically at the Array class, which has a number of delegate-driven predicate methods. Oh well!]