Expressions C # Lambda: pourquoi devrais-je les utiliser?

310

J'ai rapidement lu la documentation de Microsoft Lambda Expression .

Ce type d'exemple m'a cependant aidé à mieux comprendre:

delegate int del(int i);
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25

Pourtant, je ne comprends pas pourquoi c'est une telle innovation. C'est juste une méthode qui meurt lorsque la "variable de méthode" se termine, non? Pourquoi devrais-je utiliser cela au lieu d'une vraie méthode?

Patrick Desjardins
la source
3
Pour ceux d'entre vous qui viennent sur cette page et ne savent pas ce qu'est un delegateC #, je suggère fortement de lire ceci avant de lire le reste de cette page: stackoverflow.com/questions/2082615/…
Kolob Canyon

Réponses:

282

Les expressions lambda sont une syntaxe plus simple pour les délégués anonymes et peuvent être utilisées partout où un délégué anonyme peut être utilisé. Cependant, l'inverse n'est pas vrai; Les expressions lambda peuvent être converties en arborescences d'expression, ce qui permet une grande partie de la magie comme LINQ to SQL.

Voici un exemple d'une expression LINQ to Objects utilisant des délégués anonymes puis des expressions lambda pour montrer à quel point elles sont plus faciles à regarder:

// anonymous delegate
var evens = Enumerable
                .Range(1, 100)
                .Where(delegate(int x) { return (x % 2) == 0; })
                .ToList();

// lambda expression
var evens = Enumerable
                .Range(1, 100)
                .Where(x => (x % 2) == 0)
                .ToList();

Les expressions lambda et les délégués anonymes ont un avantage sur l'écriture d'une fonction distincte: ils implémentent des fermetures qui peuvent vous permettre de passer l'état local à la fonction sans ajouter de paramètres à la fonction ou créer des objets à usage unique.

Les arbres d'expression sont une nouvelle fonctionnalité très puissante de C # 3.0 qui permet à une API de regarder la structure d'une expression au lieu d'obtenir simplement une référence à une méthode qui peut être exécutée. Une API n'a qu'à transformer un paramètre délégué en Expression<T>paramètre et le compilateur générera une arborescence d'expressions à partir d'un lambda au lieu d'un délégué anonyme:

void Example(Predicate<int> aDelegate);

appelé comme:

Example(x => x > 5);

devient:

void Example(Expression<Predicate<int>> expressionTree);

Ce dernier recevra une représentation de l' arbre de syntaxe abstraite qui décrit l'expression x > 5. LINQ to SQL s'appuie sur ce comportement pour pouvoir transformer des expressions C # en expressions SQL souhaitées pour le filtrage / classement / etc. côté serveur.

Neil Williams
la source
1
Sans fermetures, vous pouvez utiliser des méthodes statiques en tant que rappels, mais vous devez toujours définir ces méthodes dans une classe, ce qui augmente presque certainement l'étendue de cette méthode au-delà de l'utilisation prévue.
DK.
10
FWIW, vous pouvez avoir des fermetures avec un délégué anonyme, vous n'avez donc pas strictement besoin de lambdas pour cela. Les Lambdas sont tout simplement plus lisibles que les délégués anonymes, sans lesquels l'utilisation de Linq ferait saigner les yeux.
Benjol
138

Les fonctions et expressions anonymes sont utiles pour les méthodes ponctuelles qui ne bénéficient pas du travail supplémentaire requis pour créer une méthode complète.

Considérez cet exemple:

 string person = people.Find(person => person.Contains("Joe"));

contre

 public string FindPerson(string nameContains, List<string> persons)
 {
     foreach (string person in persons)
         if (person.Contains(nameContains))
             return person;
     return null;
 }

Ils sont fonctionnellement équivalents.

Joseph Daigle
la source
8
Comment la méthode Find () aurait-elle été définie pour gérer cette expression lambda?
Patrick Desjardins
3
Le prédicat <T> est ce que la méthode Find attend.
Darren Kopp
1
Étant donné que mon expression lambda correspond au contrat pour Predicate <T>, la méthode Find () l'accepte.
Joseph Daigle
vouliez-vous dire "string person = people.Find (persons => persons.Contains (" Joe "));"
Gern Blanston, le
5
@FKCoder, non, il ne le fait pas, bien que cela aurait pu être plus clair s'il avait dit "string person = people.Find (p => p.Contains (" Joe "));"
Benjol
84

Je les ai trouvés utiles dans une situation où je voulais déclarer un gestionnaire pour un événement de contrôle, en utilisant un autre contrôle. Pour le faire normalement, vous devez stocker les références des contrôles dans les champs de la classe afin de pouvoir les utiliser dans une méthode différente de celle créée.

private ComboBox combo;
private Label label;

public CreateControls()
{
    combo = new ComboBox();
    label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += new EventHandler(combo_SelectedIndexChanged);
}

void combo_SelectedIndexChanged(object sender, EventArgs e)
{
    label.Text = combo.SelectedValue;
}

grâce aux expressions lambda vous pouvez l'utiliser comme ceci:

public CreateControls()
{
    ComboBox combo = new ComboBox();
    Label label = new Label();
    //some initializing code
    combo.SelectedIndexChanged += (s, e) => {label.Text = combo.SelectedValue;};
}

Beaucoup plus facile.

agnieszka
la source
Dans le premier exemple, pourquoi ne pas lancer l'expéditeur et obtenir la valeur?
Andrew
@Andrew: Dans cet exemple simple, il n'est pas nécessaire d'utiliser l'expéditeur, car il n'y a qu'un seul composant en question et l'utilisation directe du champ enregistre une distribution, ce qui améliore la clarté. Dans un scénario réel, je préférerais personnellement utiliser l'expéditeur à la place également. Habituellement, j'utilise un gestionnaire d'événements pour plusieurs événements, si possible et je dois donc identifier l'expéditeur réel.
Chris Tophski
35

Lambda a nettoyé la syntaxe des délégués anonymes de C # 2.0 ... par exemple

Strings.Find(s => s == "hello");

A été fait en C # 2.0 comme ceci:

Strings.Find(delegate(String s) { return s == "hello"; });

Fonctionnellement, ils font exactement la même chose, c'est juste une syntaxe beaucoup plus concise.

FlySwat
la source
3
Ce n'est pas tout à fait la même chose - comme le souligne @Neil Williams, vous pouvez extraire l'AST d'un lambdas à l'aide d'arbres d'expression, tandis que les méthodes anonymes ne peuvent pas être utilisées de la même manière.
ljs
c'est l'un des nombreux autres avantages de lambda. Il aide à mieux comprendre le code que les méthodes anonymes. ce n'est certainement pas l'intention de créer des lambdas mais ce sont des scénarios où il peut être utilisé plus souvent.
Guruji
29

Ce n'est qu'une façon d'utiliser une expression lambda. Vous pouvez utiliser une expression lambda partout où vous pouvez utiliser un délégué. Cela vous permet de faire des choses comme ceci:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

strings.Find(s => s == "hello");

Ce code recherchera dans la liste une entrée correspondant au mot "bonjour". L'autre façon de procéder consiste à passer réellement un délégué à la méthode Find, comme ceci:

List<string> strings = new List<string>();
strings.Add("Good");
strings.Add("Morning")
strings.Add("Starshine");
strings.Add("The");
strings.Add("Earth");
strings.Add("says");
strings.Add("hello");

private static bool FindHello(String s)
{
    return s == "hello";
}

strings.Find(FindHello);

MODIFIER :

En C # 2.0, cela pourrait être fait en utilisant la syntaxe de délégué anonyme:

  strings.Find(delegate(String s) { return s == "hello"; });

Lambda a considérablement nettoyé cette syntaxe.

Scott Dorman
la source
2
@Jonathan Holland: Merci pour la modification et l'ajout de la syntaxe de délégué anonyme. Il complète bien l'exemple.
Scott Dorman
qu'est-ce
qu'un
1
@HackerMan, pensez à un délégué anonyme comme une fonction qui n'a pas de "nom". Vous définissez toujours une fonction, qui peut avoir une entrée et une sortie, mais comme c'est un nom, vous ne pouvez pas vous y référer directement. Dans le code ci-dessus, vous définissez une méthode (qui prend a stringet retourne a bool) comme paramètre de la Findméthode elle-même.
Scott Dorman
22

Microsoft nous a donné un moyen plus propre et plus pratique de créer des délégués anonymes appelés expressions Lambda. Cependant, il n'y a pas beaucoup d'attention accordée à la partie expressions de cette déclaration. Microsoft a publié un espace de noms complet, System.Linq.Expressions , qui contient des classes pour créer des arborescences d'expressions basées sur des expressions lambda. Les arbres d'expression sont constitués d'objets qui représentent la logique. Par exemple, x = y + z est une expression qui pourrait faire partie d'une arborescence d'expression dans .Net. Prenons l'exemple (simple) suivant:

using System;
using System.Linq;
using System.Linq.Expressions;


namespace ExpressionTreeThingy
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, int>> expr = (x) => x + 1; //this is not a delegate, but an object
            var del = expr.Compile(); //compiles the object to a CLR delegate, at runtime
            Console.WriteLine(del(5)); //we are just invoking a delegate at this point
            Console.ReadKey();
        }
    }
}

Cet exemple est trivial. Et je suis sûr que vous pensez: "C'est inutile car j'aurais pu créer directement le délégué au lieu de créer une expression et de la compiler au moment de l'exécution". Et tu aurais raison. Mais cela fournit la base des arbres d'expression. Il existe un certain nombre d'expressions disponibles dans les espaces de noms Expressions, et vous pouvez créer les vôtres. Je pense que vous pouvez voir que cela pourrait être utile lorsque vous ne savez pas exactement ce que l'algorithme devrait être au moment de la conception ou de la compilation. J'ai vu un exemple quelque part pour l'utiliser pour écrire une calculatrice scientifique. Vous pouvez également l'utiliser pour les systèmes bayésiens , ou pour la programmation génétique(AI). Quelques fois dans ma carrière, j'ai dû écrire des fonctionnalités de type Excel qui permettaient aux utilisateurs de saisir des expressions simples (addition, soustrations, etc.) pour opérer sur les données disponibles. Dans pre-.Net 3.5, j'ai dû recourir à un langage de script externe à C #, ou j'ai dû utiliser la fonctionnalité d'émission de code en réflexion pour créer du code .Net à la volée. Maintenant, j'utiliserais des arbres d'expression.

Jason Jackson
la source
12

Cela évite d'avoir à définir des méthodes qui ne sont utilisées qu'une seule fois dans un endroit spécifique loin de l'endroit où elles sont utilisées. Les bonnes utilisations sont en tant que comparateurs d'algorithmes génériques tels que le tri, où vous pouvez ensuite définir une fonction de tri personnalisée dans laquelle vous invoquez le tri plutôt que de vous éloigner pour vous forcer à chercher ailleurs pour voir sur quoi vous triez.

Et ce n'est pas vraiment une innovation. LISP a des fonctions lambda depuis environ 30 ans ou plus.

workmad3
la source
6

Vous pouvez également trouver l'utilisation d'expressions lambda dans l'écriture de codes génériques pour agir sur vos méthodes.

Par exemple: Fonction générique pour calculer le temps pris par un appel de méthode. (c'est Actionà dire ici)

public static long Measure(Action action)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    action();
    sw.Stop();
    return sw.ElapsedMilliseconds;
}

Et vous pouvez appeler la méthode ci-dessus en utilisant l'expression lambda comme suit,

var timeTaken = Measure(() => yourMethod(param));

L'expression vous permet également d'obtenir la valeur de retour de votre méthode et de paramétrer

var timeTaken = Measure(() => returnValue = yourMethod(param, out outParam));
Gunasekaran
la source
5

L'expression lambda est un moyen concis de représenter une méthode anonyme. Les méthodes anonymes et les expressions Lambda vous permettent de définir l'implémentation de la méthode en ligne, cependant, une méthode anonyme vous oblige explicitement à définir les types de paramètres et le type de retour pour une méthode. L'expression lambda utilise la fonction d'inférence de type de C # 3.0 qui permet au compilateur d'inférer le type de la variable en fonction du contexte. C'est très pratique car cela nous fait économiser beaucoup de frappe!

Vijesh VP
la source
5

Une expression lambda est comme une méthode anonyme écrite à la place d'une instance déléguée.

delegate int MyDelagate (int i);
MyDelagate delSquareFunction = x => x * x;

Considérez l'expression lambda x => x * x;

La valeur du paramètre d'entrée est x (sur le côté gauche de =>)

La logique de la fonction est x * x (sur le côté droit de =>)

Le code d'une expression lambda peut être un bloc d'instructions au lieu d'une expression.

x => {return x * x;};

Exemple

Remarque: Funcest un délégué générique prédéfini.

    Console.WriteLine(MyMethod(x => "Hi " + x));

    public static string MyMethod(Func<string, string> strategy)
    {
        return strategy("Lijo").ToString();
    }

Références

  1. Comment un délégué et une interface peuvent-ils être utilisés de manière interchangeable?
LCJ
la source
4

La plupart du temps, vous n'utilisez la fonctionnalité qu'à un seul endroit, donc créer une méthode encombre la classe.

Darren Kopp
la source
3

C'est une façon de prendre une petite opération et de la placer très près de l'endroit où elle est utilisée (un peu comme déclarer une variable près de son point d'utilisation). Ceci est censé rendre votre code plus lisible. En anonymisant l'expression, vous rendez également beaucoup plus difficile pour quelqu'un de casser votre code client si la fonction est utilisée ailleurs et modifiée pour «l'améliorer».

De même, pourquoi avez-vous besoin d'utiliser foreach? Vous pouvez tout faire dans foreach avec une boucle for simple ou en utilisant directement IEnumerable directement. Réponse: vous n'avez pas besoin mais cela rend votre code plus lisible.

socle
la source
0

L'innovation réside dans le type sécurité et transparence. Bien que vous ne déclariez pas de types d'expressions lambda, elles sont déduites et peuvent être utilisées par la recherche de code, l'analyse statique, les outils de refactoring et la réflexion d'exécution.

Par exemple, avant d'avoir pu utiliser SQL et obtenir une attaque par injection SQL, car un pirate a transmis une chaîne là où un nombre était normalement attendu. Maintenant, vous utiliseriez une expression lambda LINQ, qui est protégée contre cela.

Il n'est pas possible de créer une API LINQ sur des délégués purs, car cela nécessite de combiner les arborescences d'expressions avant de les évaluer.

En 2016, la plupart des langages populaires prennent en charge l' expression lambda , et C # a été l'un des pionniers de cette évolution parmi les langages impératifs traditionnels.

battlmonstr
la source
0

C'est peut-être la meilleure explication des raisons d'utiliser les expressions lambda -> https://youtu.be/j9nj5dTo54Q

En résumé, il s'agit d'améliorer la lisibilité du code, de réduire les risques d'erreurs en réutilisant plutôt qu'en répliquant le code et en tirant parti de l'optimisation qui se déroule en coulisses.

Coffeeeee
la source
0

Le plus grand avantage des expressions lambda et des fonctions anonymes est le fait qu'elles permettent au client (programmeur) d'une bibliothèque / framework d'injecter des fonctionnalités au moyen de code dans la bibliothèque / framework donnée (comme c'est le LINQ, ASP.NET Core et beaucoup d'autres) d'une manière que les méthodes régulières ne peuvent pas. Cependant, leur force n'est pas évidente pour un seul programmeur d'application mais pour celui qui crée des bibliothèques qui seront ensuite utilisées par d'autres qui voudront configurer le comportement du code de bibliothèque ou celui qui utilise les bibliothèques. Le contexte d'utilisation efficace d'une expression lambda est donc l'utilisation / la création d'une bibliothèque / d'un framework.

De plus, comme ils décrivent du code à usage unique, ils n'ont pas besoin d'être membres d'une classe où cela entraînera une plus grande complexité du code. Imaginez que vous deviez déclarer une classe avec un focus flou à chaque fois que nous voulions configurer le fonctionnement d'un objet de classe.

Grigoris Dimitroulakos
la source