Loops, Conversions and Lambdas

I ran across some old code today while fixing a bug and was able to simplify it quite a bit using the latest version of C# – 3.5 (as well as fix the bug!).  Changing the code reminded me of where we (.NET developers) came from and how we've gotten to where we are today.

Data Conversion

For the sake of argument, let's say I have a double array.  I need to use this double array in a UI routine that expects a string[] (a representation of my doubles).  Back in .NET 1.x, converting this to a string[] was pretty simple (and we're ignoring culture issues in the interest of simplicity):

   1: // convert double[] to string[]
   2: string[] sa = new string[data.Length];
   3: for (int i = 0; i < data.Length; i++)
   4: {
   5:     sa[i] = data[i].ToString();
   6: }

.NET 2.0

When .NET 2.0 hit the scene, we got generics.  Since converting an array of data from one type to another was so common, Microsoft took advantage of generics and gave us Array.ConvertAll():

   1: public static TOutput[] ConvertAll<TInput, TOutput>(TInput[] array, Converter<TInput, TOutput> converter);

This takes an array of some type (TInput[]) and produces an array of a different type (TOutput).  The second parameter is a delegate that does the conversion routine.  Array.ConvertAll creates your output array and loops through your input array, calling the method pointed to by your delegate for each member to do the conversion.  A simple example would be:

   1: string[] genericSA = Array.ConvertAll(data, new Converter<double, string>(DoubleToStringConverter));
   2:  
   3: private static string DoubleToStringConverter(double d)
   4: {
   5:     return d.ToString();
   6: }

Anonymous Methods

So if we consider our first example, we took lines 2-6 and trimmed them down to one line of code (Array.ConvertAll).  But, in the process, we had to create that conversion method that contained the logic from line 5.  Microsoft decided that it would be nice to just sort of "inline" your method logic right in the code.  That's where anonymous methods come in.

The second parameter to Array.ConvertAll has to be a delegate of type Converter<TInput,TOutput> – or more specifically in our case, Converter<double,string>.  Since the compiler knows this, why require the developer to type in a whole separate method along with a delegate creation statement?  With anonymous methods, we can put our conversion code right in the call to Array.ConvertAll:

   1: string[] sa2 = Array.ConvertAll(data, delegate(double d) { return d.ToString(); });

Now we're getting some pretty clean code!  Our conversion code is right there inside our call to ConvertAll (easier for reading and code reviews).  This code will compile to almost the exact same code as earlier (where we had our own method and delegate conversion) because internally, the C# compiler will actually create a separate method (anonymously!) with our "return d.ToString()" code and will also generate a new Converter<double, string> delegate to pass to the ConvertAll method.  Nice!

Lambdas

The final icing on the cake for this conversion is the introduction of lambdas.  Lambdas seem a little scary at first, but if you've followed along with this example, you'll see how the lambda syntax has been implemented.

Like the introduction of anonymous methods, lambdas help us reduce the amount of code we write by inferring details that exist in your code.  Like our anonymous method example where the compiler will create the anonymous method and delegate call, a lambda will do the same thing.  So if we look at our conversion code above:

   1: delegate(double d) { return d.ToString(); }

The C# compiler knows we need a delegate that takes a double (since we're passing in a double[]) and returns a string (the returning of a string is inferred because the anonymous method returns a string).  If it knows all that, why make the developer type in "delegate" and the type of data ("double")?  Enter lambdas.  The above code can now be reduced to the following lambda expression:

   1: (d) => d.ToString()

If you only have a single argument, you can skip the parenthesis and simply enter:

   1: d => d.ToString()

The "return" is implied.  Microsoft says the "=>" operator is read as "goes to".  So our example reads "d goes to d.ToString()".  As before, this code will compile to almost exactly the same code as our first Array.ConvertAll example using a separate method and delegate creation (and exactly the same code as when we used anonymous methods).  But this time, we let the compiler to most of the plumbing.

End Result

We've taken our original 1.x code of:

   1: string[] sa = new string[data.Length];
   2: for (int i = 0; i < data.Length; i++)
   3: {
   4:     sa[i] = data[i].ToString();
   5: }

And have been able to reduce it down to the following C# 3.5 code:

   1: string[] lambdaSA = Array.ConvertAll(data, d => d.ToString());

I hope this little history has given you a good overview of how we moved from delegates, to anonymous methods and finally to lambdas.  Lambda expressions provide a lot of power to us and you should at least work on being able to read them as you'll see them more and more as .NET 3.5 becomes mainstream.

Technorati Tags: ,,

4 Comments

Comments have been disabled for this content.