Last week at the first run of the new .NET 4 programming course, the topic that students found the hardest to understand was lambda expressions. This is not surprising, as lambda expressions are hard to understand. They can be useful though, especially if you are using Windows Azure. So, let me try to explain them. I’m going to take it one small step at a time (you might want to get a cup of coffee).
Let’s say you have the simple class shown below.
class Calculator
{
public static double Add(double x, double y)
{
return x + y;
}
}
There’s nothing special here. To call the Add() method, use the following code:
double answer;
answer = Calculator.Add(2, 3);
Console.WriteLine(answer); // outputs 5
If you wanted to, you could invoke the method using a delegate. A delegate is “an object-oriented, type-safe pointer to a function” (I read that somewhere). To use the delegate, you first need to define it as shown below.
delegate double MathDelegate(double x, double y);
This delegate becomes a data type in your program. Like any other data type, you can declare a variable of the delegate’s type. You don’t point that variable to an object though, you point it at a function. The function must match the delegate’s signature (in this case it has to be a function that returns a double and takes two doubles as arguments). Once the delegate instance is pointing at the function, use the Invoke() method to call the function supplying the arguments, and the result is returned. The code is shown below.
MathDelegate AddFunction = Calculator.Add;
answer = AddFunction.Invoke(4, 5);
Console.WriteLine(answer); // outputs 9
Now you’re thinking, “but why would I ever do that?”. Patience, grasshopper.
Notice, the delegate in the prior code is pointing to the Add() method which exists in the Calculator class. You could create what’s call an anonymous function inline and point a delegate instance to it. An anonymous function is a function without a name. It is created with the delegate keyword in C#. In the code below, an anonymous function is created to multiply, instead of add, the numbers.
MathDelegate MultiplyFunction =
delegate(double x, double y) { return x * y; };
answer = MultiplyFunction.Invoke(6, 7);
Console.WriteLine(answer); // outputs 42
The following fragment is the important part of the code above:
delegate(double x, double y) { return x * y; };
The delegate keyword is used to define the function. The function has two arguments (x and y) and a function body where the numbers are multiplied. What it doesn’t have is a name. Since it is assigned to the delegate instance, it can be invoked through the delegate as shown above.
There’s a shorter syntax to defining the anonymous function in C#. It uses the => operator. The sample below does exactly the same thing as the prior example (only it divides the numbers).
MathDelegate DivideFunction = (x, y) => x / y;
answer = DivideFunction.Invoke(8, 4);
Console.WriteLine(answer); // outputs 2
You may be wondering how the system knows x and y are doubles. That is inferred from the argument data types defined in the delegate MathDelegate.
Now, let’s isolate these two important lines:
MathDelegate MultiplyFunction =
delegate(double x, double y) { return x * y; };
MathDelegate DivideFunction = (x, y) => x / y;
Both lines above are creating anonymous functions, and both anonymous functions match the signature of the delegate defined earlier. The second one is just more compact.
Now you’re thinking, “are we done yet?”. No, there’s more.
Inside the Calculator class you could add the following method which expects three arguments: the two numbers you are going to do math on, and a MathDelegate that is going to do the work.
public static double Evaluate(
MathDelegate mathFunction, double x, double y)
{
return mathFunction.Invoke(x, y);
}
Notice, inside the Evaluate() method the function passed as an argument is simply invoked.
Now, you can call the Evaluate() method using the following syntax:
MathDelegate DivideFunction = (x, y) => x / y;
answer = Calculator.Evaluate(DivideFunction, 100, 10);
Console.WriteLine(answer); // outputs 10
Remember, the Evaluate() method is inside the Calculator class.
In the above code, DivideFunction is just a temporary variable. You don’t need it and could just define the anonymous function inside the argument list when calling the Evaluate() method as shown below.
answer = Calculator.Evaluate((x, y) => x / y, 144, 12);
Console.WriteLine(answer); // outputs 12
Hooray, a lambda expression! A lambda expression is an anonymous function passed as an argument to another function.
But wait, there’s even more. Go get another cup of coffee.
Let’s say you want to make the Calculator more flexible, so not only can you use Evaluate() to do math on two numbers, but also to do math on a single number. For example, you want to be able to square or cube a number, as well as add or multiple two numbers. To do this, you can overload the Evaluate() method. You could define more delegates as shown earlier, but there’s an easier and more confusing way 🙂 In .NET, there is a generic type called Func<> that is used to define delegates in a flexible way. When using Func<>, you specify the arguments and the return type. This defines the generic delegate.
Recall, earlier the MathDelegate type was defined as shown below.
delegate double MathDelegate(double x, double y);
Using the Func<> generic type, you could define the delegate as follows without creating your own type:
Func<double, double, double > mathFunction
This creates a generic delegate with two arguments of the type double, and it returns a double. The last type specified is the return type.
So, let’s now add the overloaded Evaluate() method to the Calculator class as shown below.
public static double Evaluate(
Func<double, double> mathFunction, double x)
{
return mathFunction.Invoke(x);
}
In the code below, the Evaluate() methods and lambda expressions are used to divide two numbers or to square a single number.
// Here two numbers are divided
answer = Calculator.Evaluate((x, y) => x / y, 144, 12);
Console.WriteLine(answer); // outputs 12
// Here a single number is squared
answer = Calculator.Evaluate((x)=> x*x, 12);
Console.WriteLine(answer); // outputs 144
If you wanted to, you could create more overloads to do math on integers, dates, arrays, collections, Orders and so on. The client code is responsible for supplying the lambda expressions that determine what is actually done to the data.
Some of you might be thinking, “this is just polymorphism, and I don’t need lambda expressions for this.” You would be correct, but lambda expressions provide another way of making code more flexible.
Hopefully, this makes some sense. You can download the code from my web site at http://www.drehnstrom.com/downloads/LambdaExpressions.zip.
Doug