Introduction
At first glance, a lambda expression looks like something your professor may have written on the blackboard in calculus class, but in reality, it is "syntactic sugar" built into the C# language over delegates. In this article, we will begin to explore the nuances of lambda expressions by seeing how they they relate to delegates and how they can be used to implement a popular mathematical formula, the quadratic equation.
What is a delegate?
A delegate is nothing more than a function pointer in C#. Unlike the function that sits in your C# class, a delegate can be passed around like any other variable or property. Why would you want to pass around a function? The advantage of passing around a function, is that you can dynamically have it execute wherever you want inside of your code. Jeremy Miller just wrote a great article in MSDN (Oct 2009) on how you can use lamdbas (a.k.a. delegates) for various tasks such as helping you reduce code duplication, passing expressions into filters, generating calls from a separate thread into your UI thread, and more. Delegates have opened C# up to the world of functional programming, so we .NET programmers now have at our disposal the richness of object-oriented mechanisms along with the power of functional constructs.
Delegates describe the signature of the function you want to pass around your program. In the case of the quadratic formula, we need to pass in 3 parameters to produce our resulting roots. The delegate for a quadratic looks something like this:
delegate double QuadraticDelegate(double a, double b, double c);
Note that the delegate differs from an actual function in that you need to "new up" a delegate instance and give it a function. The function can either take the form of an existing method in your class, or it can be an anonymous method. An anonymous method defines the delegate without requiring an existing method. In an anonymous method, you simply define the implementation when you are constructing the delegate instance.
In our first attempt at implementing the quadratic formula, we use 3 anonymous methods. The first anonymous method calculates the discriminant of the quadratic formula. The discriminant is then used by the quadratic formula anonymous methods to calculate the two roots of the equation. Note that none of the delegates actually implement the formula, they just create the implementations needed to run against parameters a,b, and c. Implementing the delegate is performed the same way you would call any other method, by passing values to the delegate.
Listing 1 - Implementing the quadratic formula with anonymous methods
delegate double QuadraticDelegate(double a, double b, double c); static void Method1() { Console.WriteLine("Method #1");
// create an anonymous method to calculate the discriminant QuadraticDelegate discriminant = delegate(double a, double b, double c) {return (b * b - 4d * a * c);};
// create an anonymous method for the quadratic functions QuadraticDelegate quadratic1 = delegate(double a, double b, double c) { return (-b + Math.Sqrt(discriminant(a, b, c))) / (2 * a); }; QuadraticDelegate quadratic2 = delegate(double a, double b, double c) { return (-b - Math.Sqrt(discriminant(a, b, c))) / (2 * a); }; // calculate the quadratic roots for x^2 + 5x + 6 by implementic the delegates instances. double val1 = quadratic1(1d, 5d, 6d); double val2 = quadratic2(1d, 5d, 6d); Console.WriteLine("Roots are {0}, {1}", val1, val2); // calculate the quadratic roots for x^2 + 40x + 6 double val3 = quadratic1(1d, 40d, 6d); double val4 = quadratic2(1d, 40d, 6d);
Console.WriteLine("Roots are {0}, {1}", val3, val4); Console.ReadLine(); } |
Using Lambda Expressions to Implement delegates
A lambda expression is a fancy way of writing anonymous methods. First let's look at a simple lambda expression and understand what it does.
The expression
x => x + 1 can simply be redefined as the anonymous method delegate(int x) { return (x + 1);}
of course, for the lambda expression to be fully resolved, you would need to assign the lambda expression to the delegate it is implementing.
delegate int SumInt(int num);
SumInt result = x => x + 1;
otherwise we have no way of resolving whether x is an integer, double, or other type. There are a few ways you can write the lambda expression x => x + 1 as shown in listing 2 below, all are equivalent.
Listing 2 - Multiple ways to write the lambda expression x => x + 1
delegate int SumInt(int num); static void LambdaSample() { SumInt val1 = (x) => x + 1; Console.WriteLine("val = {0}", val1(1)); Console.ReadLine(); SumInt val2 = x => x + 1; Console.WriteLine("val = {0}", val2(1)); Console.ReadLine(); SumInt val3 = (int x) => x + 1; Console.WriteLine("val = {0}", val3(1)); Console.ReadLine(); } |
So now how do we write our equivalent quadratic expressions using lambdas? Since we are passing in three parameters (a,b,c), we'll need to place this expression on the left side. On the right side, we will place our quadratic expression utilizing these parameters.
QuadraticDelegate quadratic1 = (a, b, c) => (-b + Math.Sqrt(discriminant(a, b, c))) / (2 * a);
The code above is equivalent to our anonymous method below , but some might argue, a lot easier to read (at least for those professors teaching advanced calculus).
QuadraticDelegate quadratic1 = delegate(double a, double b, double c) { return (-b + Math.Sqrt(discriminant(a, b, c))) / (2 * a); };
The full equivalent quadratic example using lambdas is shown in listing 3
Listing 3 - Implementing the quadratic equation using lambda expressions
delegate double QuadraticDelegate(double a, double b, double c); static void Method2() { Console.WriteLine("Method #2");
// create an anonymous delegate to calculate the discriminant using a lambda expression QuadraticDelegate discriminant = (a, b, c) => (b * b - 4d * a * c); // create an anonymous delegate for the quadratic functions using lambdas QuadraticDelegate quadratic1 = (a, b, c) => (-b + Math.Sqrt(discriminant(a, b, c))) / (2 * a); QuadraticDelegate quadratic2 = (a, b, c) => (-b - Math.Sqrt(discriminant(a, b, c))) / (2 * a); // calculate the quadratic roots for x^2 + 5x + 6 double val1 = quadratic1(1d, 5d, 6d); double val2 = quadratic2(1d, 5d, 6d); Console.WriteLine("Roots are {0}, {1}", val1, val2); // calculate the quadratic roots for x^2 + 40x + 6 double val3 = quadratic1(1d, 40d, 6d); double val4 = quadratic2(1d, 40d, 6d); Console.WriteLine("Roots are {0}, {1}", val3, val4); Console.ReadLine(); } |
If we now look at the quadratic using lambdas reinterpreted in redgate's reflector, it does in fact confirm that lambdas are nothing more than anonymous delegates. Our reflector code looks exactly like the code in Method1 shown in listing 1:
Listing 4 - Method Containing Lambda Expressions Reflected through RedGate's Reflector
Function Templates
It is sort of painful to have to declare the delegate and then assign it every time you need to use a lambda expression. Microsoft has created these cool templates called Func that seem to generate the delegates for you. We can therefore rewrite listing 3 as follows and avoid needing to create a quadratic delegate outside the method.
Listing 5 - Rewriting the quadratic implementation using the Func<T> delegate
static void Method3() { Console.WriteLine("Method #3");
// create an anonymous delegate to calculate the descriminant Func<double, double, double, double> descriminant = (a, b, c) => (b * b - 4d * a * c); // create an anonymous delegate for the quadratic functions Func<double, double, double, double> quadratic1 = (a, b, c) => (-b + Math.Sqrt(descriminant(a, b, c))) / (2 * a); Func<double, double, double, double> quadratic2 = (a, b, c) => (-b - Math.Sqrt(descriminant(a, b, c))) / (2 * a);
// calculate the quadratic roots for x^2 + 5x + 6 double val1 = quadratic1(1d, 5d, 6d); double val2 = quadratic2(1d, 5d, 6d); Console.WriteLine("Roots are {0}, {1}", val1, val2); // calculate the quadratic roots for x^2 + 40x + 6 double val3 = quadratic1(1d, 40d, 6d); double val4 = quadratic2(1d, 40d, 6d); Console.WriteLine("Roots are {0}, {1}", val3, val4); Console.ReadLine(); } |
The first 3 double parameters in the Func template are for the 3 doubles passed into the function. The last double parameter is the return value type. Microsoft would have had to create a different Func template for varying number of parameters, so I was curious at what point they stopped creating Func<T> methods. After all there could be an infinite number of Func<T1, T2, T3, T4, T5, ...Result>. The MSDN documentation and intellisense shows a maximum of 4, so I guess my sample came close to hitting the limit. Anyway, below are the results of all three methods of producing the quadratic, and as expected all produce equivalent results .
Figure 1 - Results of running all three delegate methods
Conclusion
This is just the tip of the iceberg of what you can do with lambda expressions, but hopefully this article helps clarify what a lambda expression is used for in C#. Lambda expressions start to get more interesting when you start to take advantage of the closure principle or when you use lambdas with powerful technologies such as LINQ. In the meantime, don't be afraid to delegate your knowledge to your programming tasks and harness the power of lambda in C# and .NET.