What Is New in C# 3.0 - Part 5 (Lambda Expressions)

   I was about to start this article with theoretical explanation and historical background of lambda expressions but while I was searching for articles explaining some subtle details I came upon some very interesting comments. Some people were complaining that functional programming (mainly lambdas) are not natural to C# and they can confuse developers with no theoretical (i.e. university) background. Luckily the C# community (unlike the Java community) is very open-minded to new features and opinions are about 10:1 in favor of lambdas (if you take a look at discussions about new features in Java you will see that like 3:2 are against ANY changes in the language itself). So I decided to write the article with examples of how lambdas are actually simpler and easier even if you do not understand how they work internally.

   Lets look at simple and relatively trivial example. Imagine that we need to sort some list of strings in a non-conventional manner. To make the example simpler our non conventional manner will be reverse order. Sorting libraries in all languages have taken care of this problem so all sort methods provide an overload with a way of specifying how to compare the objects. Lets first take a look at the Java way (a.k.a. The OOP way).

   The way to specify how to compare the objects in Java is to pass an object that implements the Comparator interface to the Collections.Sort method. First we need to declare a class (and in Java that means separate file) that implements this interface:

import java.util.Comparator;

class Momparator implements Comparator<String> {

   public int compare(String o1, String o2) {
       return -o1.compareTo(o2);
   }
}

Then we can use it like this:

       ArrayList<String> mist = new ArrayList<String>();
       
       mist.add("asdf1");
       mist.add("asdf2");
       mist.add("asdf3");
       
       Momparator momparator = new Momparator();

       Collections.sort(mist, momparator);
       
       for (String s: mist){
           System.out.println(s);
       }

   That is an OOP way to solve some non-OOP problem. Lets take a look at the C# 1.x way which happens to be very close to the C way. Yes I know C# 1.x does not have generics but I wanted to keep the examples as close as possible.

First we define a method in whatever class we want (preferably the same) that compares two strings. The method can be static.

private static int CompareStrings(string s1, string s2)
{
   return -s1.CompareTo(s2);
}

Then we can use it like this:

       List<string> mist = new List<string>();

       mist.Add("asdf1");
       mist.Add("asdf2");
       mist.Add("asdf3");

       mist.Sort(new Comparison<string>(CompareStrings));

       foreach (string s in mist)
       {
           Console.WriteLine(s);
       }

   Comparison is a delegate (part of .NET Framework) to method with the same signature as the compare method in the Java example. I think this approach is better because it does not require a new class, but only a new method. Notice that the Sort method does have an overload that accepts IComparer instance so the Java way is also supported.  Now lets take a look at the C# 2.0 way which uses anonymous methods (refer to part 0):

       List<string> mist = new List<string>();

       mist.Add("asdf1");
       mist.Add("asdf2");
       mist.Add("asdf3");

       mist.Sort(delegate(string s1, string s2) { return -s1.CompareTo(s2); });

       foreach (string s in mist)
       {
           Console.WriteLine(s);
       }

   It is the same but we used anonymous method to create the Comparison delegate. I think it is easier to read and even if it is not easier at least it is not harder and it is shorter which is always a plus. So now lets take a look at the C# 3.0 way:

       List<string> mist = new List<string> { "asdf1", "asdf2", "asdf3" }; //Collection initializer, baby!

       mist.Sort((x, y) => -x.CompareTo(y));

       foreach (string s in mist)
       {
           Console.WriteLine(s);
       }

   (x, y) is the parameter list, the return value is the result of the expression and => is a token that denotes lambda expression. The types of the parameters is inferred from the parameter of the Sort method which is the Comparison delegate. It is a delegate to a method that has 2 string arguments and returns int so everything is checked at compile time. You can also specify the types of the parameters if you like. Full intellisense support is also present. Now you see that you do not need to be an expert in functional programming neither you need to know anything about delegates to use lambda expressions. They seem pretty intuitive.

   Like anonymous methods lambdas are closures so you have access to the scope of the method that creates the lambda. How about this way to control the direction of the sorting:

       List<string> mist = new List<string> { "asdf1", "asdf2", "asdf3" }; //Collection initializer, baby!

       int mirection = -1;

       mist.Sort((x, y) => mirection * x.CompareTo(y));

       foreach (string s in mist)
       {
           Console.WriteLine(s);
       }

   Notice that mirection is not in the parameter list. So what do lambda expressions give us that anonymous method does not? First of all they give us better syntax due to type inference and the fact that lambda expressions can be not only statements but also expressions. However the real power of lambda expressions is that they can be compiled to expression trees. We will deal with expression trees in the next part of the series.

   Internally lambda expressions compile to anonymous methods but you see that you can use them without any knowledge of what anonymous method is. You will see how natively they fit in the LINQ query syntax. If you want to know how lambdas work internally refer to part 0 of the series and to the next part (expression trees) because lambdas can be both depending on the context.

   Lambda expressions can compile to any delegate that matches the signature but there are some delegates provided especially for lambdas and these are the Func delegates – Func<TResult>, Func<T1, TResult>, Func<T1, T2, TResult>, Func<T1, T2, T3, TResult>, Func<T1, T2, T3, T4 TResult>. These delegates are supposed to be used in methods that accept lambda expressions as parameters.

   The type inference rules are relatively complex but not impossible to grasp. Taken from the C# 3.0 Language Specification:

'The following example demonstrates how anonymous function type inference allows type information to “flow” between arguments in a generic method invocation. Given the method

static Z Method<X,Y,Z>(X value, Func<X,Y> f1, Func<Y,Z> f2) {
return f2(f1(value));
}

type inference for the invocation

double seconds = Method("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);

proceeds as follows: First, the argument "1:15:30" is related to the value parameter, inferring X to be string. Then, the parameter of the first anonymous function, s, is given the inferred type string (because it is X), and the expression TimeSpan.Parse(s) is related to the return type of f1, inferring Y to be System.TimeSpan. Finally, the parameter of the second anonymous function, t, is given the inferred type System.TimeSpan (because it is Y), and the expression t.TotalSeconds is related to the return type of f2, inferring Z to be double. Thus, the result of the invocation is of type double.'

If we wanted we could write it like this:

var seconds = Method("1:15:30", s => TimeSpan.Parse(s), t => t.TotalSeconds);

and seconds type would be inferred as double.

Cya and stay tuned for part 6.
Tags:   english programming 
Posted by:   Stilgar
03:29 24.02.2008

Comments:

First Previous 1 Next Last 

Posted by   Linux Fan (Unregistered)   on   22:24 24.02.2008

За мен е чест, че първият коментар е моя. Статията е супер! Сега остава само да я прочета.

Posted by   niki.valchanov   on   12:26 25.02.2008

Поредната яка статия ... keep'em commin' !!! :D :D :D

Posted by   A-post-all (Unregistered)   on   14:06 25.02.2008

Оракула Стилгар, използвайки ламбда изразни средства за представяне на стринг теорията доказва, че времето за унищожение на Системата (слънчевата?) ще бъде в 1:15:30. На анонимна церемония на оракулите (най-накрая) на Дубле ще се делегират права да свърши тази отговорна работа. Параметрите на унищожението и резултата - неизвестени!
Като всеки уважаващ се оракул Стилгар оставя деня на апокалисиса отворен за интерпретации.

First Previous 1 Next Last 


Post as:



Post a comment: