Invited signatures
 

Lambda expressions in C# 3.0

 

Published Oct. 26, 2006
Updated Oct. 26, 2006

Author: Octavio Hernández
[email protected]
PoKRsoft, S.L.
 
Expresiones lambda en C# 3.0 (versión en español) Versión en español


 

0. Foreword

It is a great pleasure for me to contribute with this humble article to the celebration of the tenth anniversary of “El Guille”, a web site that has earned by its own right a place in the heart of many thousands of Spanish-speaking developers using Microsoft technologies. Congratulations!

 

1. Introduction

It is no secret for anybody that C#, which was initially conceived as a language targeted to subsume such different programming paradigms as procedural (imperative), object-oriented (in line with the standards set by C++ and later Java) and component-based (with two clear referents, Delphi and Visual Basic), has evolved since then in a way that attempts to take into account and integrate into the language the best ideas arisen under another points of view of programming, like dynamic languages (Perl, Python, Ruby), data manipulation languages (SQL, XML) or functional programming (LISP, ML).

Lambda expressions, one of the most important new features to be included in the next 3.0 version of C# [3] and one that offers support needed by the LINQ Project [4], are precisely a feature coming from the world of functional programming. In LISP and its dialects, like Scheme, the lambda construction is a mechanism that allows the programmer to define new functions. For instance, the expression:

        (define square (lambda (x) (* x x)))

 

defines a variable named square, whose value is a function that produces the square of its argument. One of the most prominent characteristics of functional languages, to which the architects of .NET Framework give great importance [2], is that lambda functions can be treated not only as pieces of code that can be invoked, but also as “common and earthly” values that can be manipulated by other code blocks. Using a term I heard the first time more than 20 years ago from my tutor of then, Dr. Luciano Garcia, lambda functions facilitate metaprogramming – the writing of programs capable of manipulating other programs.

This article describes the implementation of lambda expressions in C# 3.0, concentrating mainly on its exploitation as code blocks; issues related to the representation as data and further programmatic manipulation of lambda expressions will be only introduced for the sake of completeness, and a more in-depth discussion of them will be left for a next installment of this column.

 

2. Lambda expressions as code entities

The natural way of implementing a definition like that of the square lambda function in C# would be through a delegate type and a delegate instance [5]:

        delegate T Mapping<T>(T x);

        Mapping<int> square = delegate(int x) { return x * x; };

 

The first line above defines a generic delegate type named Mapping. From this type model, we will be able to create delegate instances for functions that produce a value of type T from another value of the same type T (that is, functions that map – remember the term, which we will be using more frequently in the near future – a value of type T into another value of the same type).

The second line of code defines an instance of the previous delegate type that “points” to a function that returns the square of an integer number. In this sentence we make use of another feature added to C# in version 2.0, anonymous methods, that allow the in-line specification of the block of code that defines the functionality we wish to associate to the delegate instance. In the original version of C# we would have had to define the function explicitly (besides, the delegate couldn’t have been generic):

        static int Square(int x)

        {

            return x * x;

        }

        Mapping<int> square2 = Square;

 

Although anonymous methods offer a more compact and direct notation, their syntax is still quite verbose and imperative by nature. In particular, when defining an anonymous method it is necessary:

·         To explicitly use the keyword delegate.

·         To explicitly specify the types of the parameters, without any possibility of compiler inference from the usage context.

·         In the relatively frequent case when the body of the function is a simple return sentence followed by an expression that calculates the result (as in the case of our square), the excess in syntax is even more evident.

Lambda expressions can be seen as an extension of anonymous methods that offers a more concise and functional syntax. The syntax of a lambda expression consists of a list of variables-parameters, followed by the implication (function application) sign =>, then followed by the expression or block that implements the desired functionality. For instance, our definition of square could be rewritten using a lambda expression like this:

        Mapping<int> square3 = x => x * x;

 

Shorter - impossible, right? This sentence expresses in quite a succinct and natural way that square3 references a function that, given some x, produces x multiplied by itself. In this case, the compiler automatically infers from the context that the type of the parameter and the result should be int. We could have indicated that explicitly:

        Mapping<int> square4 = (int x) => x * x;

 

If the type of the parameter is specified, or if the expression has more than one parameter (be their type indicated explicitly or not) parenthesis are mandatory. On the other hand, in the right side of the implication we can place a simple expression, as previously, or a block of sentences of any complexity (as long as it produces a result of the correct type):

        Mapping<int> square5 = (int x) => { return x * x; };

 

Until now we have been using our own generic delegate type Mapping<T>, but in practice most of the times we will use the different “overloads” of the predefined generic delegate type Func:

        // in System.Query.dll

        // namespace System.Query

        public delegate T Func<T>();

        public delegate T Func<A0, T>(A0 a0);

        public delegate T Func<A0, A1, T>(A0 a0, A1 a1);

        public delegate T Func<A0, A1, A2, T>(A0 a0, A1 a1, A2 a2);

        public delegate T Func<A0, A1, A2, A3, T>(A0 a0, A1 a1, A2 a2, A3 a3);

 

The Func family of types represents delegate instances that reference functions (with 0, 1, 2 or 3 arguments, respectively) that return T. Using Func, we could define a new version of square like this:

        Func<int, int> square6 = x => x * x;

 

Below we show some more examples of definition and usage of lambda expressions:

        // more examples

        static Func<double, double, double> hypotenuse =

            (x, y) => Math.Sqrt(x * x + y * y);

 

        static Func<int, int, bool> divisibleBy =

            (int a, int b) => a % b == 0;

 

        static Func<int, bool> isPrime =

            x => {

                for (int i = 2; i <= x / 2; i++)

                    if (divisibleBy(x, i))

                        return false;

                return true;

            };

 

        static void Main(string[] args)

        {

            int n = 19;

            if (isPrime(n)) Console.WriteLine(n + " is prime");

            Console.ReadLine();

        }

 

3. Lambda expressions as data entities

Considering what we’ve seen until now, anybody could think that lambda expressions are mere “syntactic sugar” added to the language in order to “sweeten” the consumption of delegates and anonymous methods. But C# 3.0 lambda expressions offer an additional possibility not available to anonymous methods, and one that plays a key role in the implementation of the LINQ (Language Integrated Query) technology, that will also be included in the next version of C# and VB and that constitutes, in the humble opinion of this author, one of the most significant advances in programming languages and systems in recent times.

This additional possibility is that of being compiled as expression trees, data objects that efficiently represent in memory the algorithm of evaluation of lambda expressions. These in-memory trees can later be easily manipulated by software (metaprogramming!), stored, transmitted, etc. Although we’ll defer a more detailed analysis of expression trees until our next installment, let’s see a small example:

        static Expression<Func<double, double, double>> hypotenuseExpr =

            (x, y) => Math.Sqrt(x * x + y * y);

 

        static void Main(string[] args)

        {

            Console.WriteLine(hypotenuseExpr);

            // prints '(x, y) => Sqrt(Add(Multiply(x, x), Multiply(y, y)))'

 

            hypotenuse = hypotenuseExpr.Compile();  // here ILAsm is generated!

            Console.WriteLine("Hypotenuse(3, 4) = " + hypotenuse(3, 4));

            // prints 'Hypotenuse(3, 4) = 5'

 

            Console.ReadLine();

        }

 

In the code fragment above, the lambda expression that calculates the hypotenuse of a right triangle is assigned not to a delegate, but to a variable of the predefined generic type Expression:

        // in System.Query.dll

        // namespace System.Expressions

        public class Expression<T> : LambdaExpression { … }

 

In this case, the compiler does not generate the ILAsm code corresponding to the expression; an object tree that represents the sequence of actions needed for expression’s evaluation is produced instead. You can get an idea of the internal structure of this tree by watching the output produced by the call to its ToString() method.

Note also that the Expression class offers a Compile() method that makes it possible to dynamically generate the executable code corresponding to the expression when needed. That is, this method serves as a “bridge” between both worlds, translating data into code when it becomes necessary to evaluate the expression.

 

4. Conclusion

Lambda expressions are one of the main new features that will be included in the next version of C#. In this article we have shown how lambda expressions have two facets of representation and usage: as code (in the form of anonymous methods) and as data (in the form of expression trees), both of which are tightly related. In our next installment we will discuss expression trees with more detail.

The source code of the example can be downloaded from this site. In order to run it, the May 2006 LINQ Preview, available at [1], must be downloaded and installed.

 

5. Acknowledgments

The author wants to thank Guillermo “El Guille” Som, not only for the opportunity of writing for his web site, but also for his sincere friendship and for the help he has always offered me whenever my “too strongly typed” brains can’t cope with this or that feature of Visual Basic .NET. ¡Gracias, Maestro!

 

6. References

  1. C# 3.0 and LINQ related resources: http://msdn.microsoft.com/csharp/future/default.aspx.
  2. Box, Don “Scheme is love”, published in MSDN Magazine, October 2005.
  3. Hernández, Octavio “What Orcas will bring: new features in C# 3.0”, published in dotNetManía Nº 24, March 2006 (in Spanish).
  4. Hernández, Octavio “What Orcas will bring: the LINQ Project”, published in dotNetManía Nº 25, April 2006 (in Spanish).
  5. Som, Guillermo “Delegates and events (parts 1 and 2)”, published in dotNetManía Nº 30 and 31, October and November 2006 (in Spanish).

 


Sample code (ZIP):

 

File with sample code: octavio_ExpresionesLambda.zip - 2.93 KB

(MD5 checksum: DE43576E155B16397E81261C836E5504)

 


Ir al índice principal de el Guille