A perfect blend of all things Dot Net
I was recently reading Programming Ruby: The Pragmatic Programmers’ Guide, Second Edition, and came across this piece of example Ruby code:
[1,3,5,7].inject(0) {|sum, element| sum+element} -> 16
[1,3,5,7].inject(1) {|product, element| product*element} -> 105
Inject is a method which acts on an array by aggregating or accumulating the values within that array. It loops through the array, and for every item in the array, it performs a function. It then saves the result for the next iteration of the loop and eventually returns the aggregated value.
In C# 1.0 you would probably write such a method like this:
int sum = 0; int[] list = new int[] { 1, 3, 5, 7 }; foreach (int item in list) { // Perform some function, then save the result sum = sum + item; }
It’s a bit long-winded, and if you wanted to make it reusable, you’d have a hard time.
In C# 3.0, you can do it just like you can in Ruby.
First, let me explain how the Ruby inject method works.
First of all, it’s really misnamed in Ruby. The method is an aggregator, or even an "accumulator". It aggregates or accumulates values. It takes two parameters - a starter value, and a block. In C# 3.0 we basically call a block a delegate, although in this case, it’s more like a Lambda Expression.
The inject method works like this:
So, let’s imagine you were trying to write the Inject method in C# 2.0. If you were to refactor the above C# 1.0 example as C# 2.0, you might use anonymous methods to write this:
// Define a delegate delegate int Aggregator(int sum, int item); static void Main(string[] args) { int sum = 0; int[] list = new int[] { 1, 3, 5, 7 }; sum = Inject(sum, list, delegate(int starterVal, int item) { return starterVal + item; }); } int Inject(int starterVal, int[] list, Aggregator aggregator) { int sum = starterVal; foreach (int item in list) { // Perform some function, then save the result sum = aggregator(sum, item); } return sum;
However, that would only work for int objects. You could really make it generic, and use the same method for other types:
// Define a delegate delegate T Aggregator<T>(T sum, T item); …
sum = Inject<int>(sum, list, delegate(int starterVal, int item) { return starterVal + item; });
... T Inject<T>(T starterVal, T[] list, Aggregator<T> aggregator) { T sum = starterVal; foreach (T item in list) { // Perform some function, then save the result sum = aggregator(sum, item); } return sum; }
It’s nicer, but the Ruby method still wins hands-down for neatness.
The good news is, the C# 3.0 compiler and the .Net 3.5 framework can now take care of most of this for you.
To start with, the Aggregator delegate is no longer necessary, because the new Func<T,T,T> delegate already defines a generic delegate which returns a value.
T Inject<T>(T starterVal, T[] list, Func<T,T,T> aggregator)
Secondly, the anonymous function using the delegate keyword can be replaced by a lambda expression like this:
(starterVal, item) => starterVal + item
Thirdly, the Inject method can be defined as an extension method so that you can call it like this:
sum = list.Inject(sum, (starterVal, item) => starterVal + item );
And fourthly, the compiler can infer the type for you, so you don’t need to specify it on calling the method.
But the even better news is that this method is already defined in the .Net framework for you, so you don’t even have to write it. Instead of being called inject, it’s called Aggregate, and is defined on the System.Linq.Enumerable<T> class.
So basically, using C# 3.0, you could reduce the above code to this:
var sum = 0; var list = new[] { 1, 3, 5, 7 }; sum = list.Aggregate(sum, (starterVal, item) => starterVal + item );
You get an extra bonus point if you spotted the extra C# 3.0 features I sneaked in there - the var keyword, and array initializers.
C# 3.0 is pretty neat huh? Move over Ruby.
Leave a reply