Expressions lambda (Guide de programmation C#)

Une expression lambda est une fonction anonyme qui peut contenir des expressions et des instructions, et peut être utilisée pour créer des délégués ou des types d'arborescence d'expression.

Toutes les expressions lambda utilisent l'opérateur lambda = >, qui se lit « conduit à ». Le côté gauche de l'opérateur lambda spécifie les paramètres d'entrée (le cas échéant) et le côté droit contient le bloc d'expression ou d'instructions. L'expression lambda x => x * x se lit « x conduit à x fois x ». Cette expression peut être assignée à un type délégué de la manière suivante :

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

Pour créer un type d'arborescence d'expression :

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

L'opérateur => a la même priorité que l'assignation (=) et il est associatif à droite.

Les lambdas sont utilisés dans les requêtes LINQ fondées sur une méthode en tant qu'arguments pour les méthodes d'opérateur de requête standard telles que Where.

Lorsque vous utilisez la syntaxe fondée sur une méthode pour appeler la méthode Where dans la classe Enumerable (de la même manière que dans LINQ to Objects et LINQ to XML) le paramètre est un type délégué System.Func<T, TResult>. Une expression lambda est la méthode la plus commode pour créer ce délégué. Lorsque vous appelez la même méthode dans, par exemple, la classe System.Linq.Queryable (comme vous le faites dans LINQ to SQL), le type de paramètre est un System.Linq.Expressions.Expression<Func> où Func est tout délégué Func comportant seize paramètres d'entrée maximum. Une expression lambda est simplement une méthode très concise pour construire cette arborescence de l'expression. Les lambdas font apparaître les appels Where comme semblables bien qu'en fait, le type d'objet créé à partir du lambda soit différent.

Dans l'exemple précédent, vous remarquez que la signature du délégué comporte un paramètre d'entrée implicitement typé int et retourne un int. L'expression lambda peut être convertie en un délégué de ce type, car elle comporte également un paramètre d'entrée (x) et une valeur de retour que le compilateur peut convertir implicitement en type int. (L'inférence de type est traitée plus en détail dans les sections suivantes.) Lorsque le délégué est appelé à l'aide d'un paramètre d'entrée de 5, il retourne un résultat de 25.

Les lambdas ne sont pas autorisés sur le côté gauche de l'opérateur is ou as.

Toutes les restrictions qui s'appliquent aux méthodes anonymes s'appliquent également aux expressions lambda. Pour plus d'informations, consultez Méthodes anonymes (Guide de programmation C#).

Lambda-expressions

Une expression lambda avec une expression sur le côté droit est appelée lambda-expression. Les lambda-expressions sont utilisées en grand nombre dans la construction d'Arborescences d'expression (C# et Visual Basic). Une lambda-expression retourne le résultat de l'expression et prend la forme de base suivante :

(input parameters) => expression

Les parenthèses sont facultatives uniquement si le lambda comporte un paramètre d'entrée ; sinon, elles sont obligatoires. Les paramètres d'entrée sont séparés par des virgules entre parenthèses :

(x, y) => x == y

Il est parfois difficile, voire impossible, pour le compilateur pour déduire les types d'entrée. Dans ce cas, vous pouvez spécifier les types explicitement comme indiqué dans l'exemple suivant :

(int x, string s) => s.Length > x

Spécifiez des paramètres d'entrée de zéro avec des parenthèses vides :

() => SomeMethod()

Notez dans l'exemple précédent que le corps d'une lambda-expression peut se composer d'un appel de méthode. Toutefois, si vous créez des arborescences d'expression qui seront consommées dans un autre domaine, tel que SQL Server, vous ne devez pas utiliser d'appels de méthode dans les expressions lambda. Les méthodes n'auront aucune signification en dehors du contexte du Common Language Runtime .NET.

Lambda-instructions

Une lambda-instruction ressemble à une lambda-expression, mais l'instruction ou les instructions sont mises entre accolades :

(input parameters) => {statement;}

Le corps d'une lambda-instruction peut se composer d'un nombre illimité d'instructions ; toutefois, en pratique, leur nombre est généralement de deux ou trois.

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Les lambda-instructions, comme les méthodes anonymes, ne peuvent pas être utilisés pour créer des arborescences d'expression.

Lambdas avec les opérateurs de requête standard

De nombreux opérateurs de requête standard comportent un paramètre d'entrée dont le type est l'un de la famille Func<T, TResult> de délégués génériques. Les délégués Func<T, TResult> utilisent des paramètres de type pour définir le nombre et le type des paramètres d'entrée ainsi que le type de retour du délégué. Les délégués Func sont très utiles pour l'encapsulation des expressions définies par l'utilisateur appliquées à chaque élément dans un jeu de données sources. Considérons par exemple le type délégué suivant :

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

Ce délégué peut être instancié comme Func<int,bool> myFunc où int est un paramètre d'entrée et bool la valeur de retour. La valeur de retour est toujours spécifiée dans le dernier paramètre de type. Func<int, string, bool> définit un délégué avec deux paramètres d'entrée, int et string et un type de retour de bool. Le délégué Func suivant, lorsqu'il est appelé, retourne la valeur true ou false pour indiquer si le paramètre d'entrée est égal à 5 :

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

Vous pouvez également fournir une expression lambda lorsque le type d'argument est Expression<Func>, par exemple dans les opérateurs de requête standard définis dans System.Linq.Queryable. Lorsque vous spécifiez un argument Expression<Func>, le lambda est compilé en une arborescence d'expression.

Un opérateur de requête standard, la méthode Count, est illustré ici :

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

Le compilateur peut déduire le type du paramètre d'entrée, ou vous pouvez également le spécifier explicitement. Cette expression lambda particulière compte les entiers (n) qui, lorsqu'ils sont divisés par deux, ont un reste de 1.

La méthode suivante produira une séquence qui contient tous les éléments du tableau numbers de nombres qui apparaissent à gauche du « 9 », car c'est le premier nombre de la séquence qui ne satisfait pas la condition :

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

Cet exemple indique comment spécifier plusieurs paramètres d'entrée en les mettant entre parenthèses. La méthode retourne tous les éléments du tableau de nombres, jusqu'à ce que soit rencontré un nombre dont la valeur est inférieure à cette position. Ne confondez pas l'opérateur lambda (=>) avec l'opérateur supérieur ou égal (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

Inférence de type dans les lambdas

Lorsque vous écrivez des lambdas, vous n'avez généralement pas à spécifier un type pour les paramètres d'entrée, car le compilateur peut déduire le type en fonction du corps du lambda, du type délégué sous-jacent et d'autres facteurs décrits dans la spécification du langage C#. Pour la plupart des opérateurs de requête standard, la première entrée est le type des éléments dans la séquence source. Donc, si vous interrogez un IEnumerable<Customer>, il en est déduit que la variable d'entrée est un objet Customer, ce qui signifie que vous avez accès à ses méthodes et propriétés :

customers.Where(c => c.City == "London");

Les règles générales pour les lambdas sont les suivantes :

  • Le lambda doit contenir le même nombre de paramètres que le type délégué.

  • Chaque paramètre d'entrée dans le lambda doit être implicitement convertible en son paramètre de délégué correspondant.

  • La valeur de retour du lambda (le cas échéant) doit être implicitement convertible en type de retour du délégué.

Notez que les expressions lambda en elles-mêmes n'ont pas de type, car le système de type commun (CTS, Common Type System) ne comporte aucun concept intrinsèque « d'expression lambda ». Toutefois, il est parfois commode de parler de manière informelle du « type » d'une expression lambda. Dans ce cas, le type fait référence au type délégué ou au type Expression dans lequel est convertie l'expression lambda.

Portée variable dans les expressions lambda

Les lambdas peuvent faire référence aux variables externes qui sont dans la portée dans la méthode ou le type englobant dans lesquels le lambda est défini. Les variables capturées de cette manière sont stockées pour une utilisation dans l'expression lambda, même si les variables se trouveraient sinon en dehors de la portée et seraient récupérées par le garbage collector. Une variable externe doit être assignée de manière précise pour pouvoir être utilisée dans une expression lambda. L'exemple suivant illustre ces règles :

delegate bool D();
delegate bool D2(int i);

class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };

        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();

        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }

    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);

        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);

        // Output: True
        Console.WriteLine(result);
           
        Console.ReadKey();
    }
}

Les règles suivantes s'appliquent à la portée des variables dans les expressions lambda :

  • Une variable capturée sera récupérée par le garbage collector uniquement lorsque le délégué qui y fait référence sort de la portée.

  • Les variables introduites dans une expression lambda ne sont pas visibles dans la méthode externe.

  • Une expression lambda ne peut pas capturer directement un paramètre ref ou out dans une méthode englobante.

  • Une instruction return dans une expression lambda ne provoque pas le retour de la méthode englobante.

  • Une expression lambda ne peut pas contenir une instruction goto, une instruction break ou une instruction continue dont la cible se trouve à l'extérieur du corps ou dans le corps d'une fonction anonyme contenue.

Spécification du langage C#

Pour plus d'informations, consultez la Spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.

Chapitre proposé

Delegates, Events, and Lambda Expressions dans C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Voir aussi

Référence

Méthodes anonymes (Guide de programmation C#)

is (référence C#)

Concepts

Guide de programmation C#

Arborescences d'expression (C# et Visual Basic)

Autres ressources

LINQ (Language-Integrated Query)

Expressions lambda récursives