Quelle est la différence entre les lambdas et les délégués dans le .NET Framework?

86

On me pose souvent cette question et j'ai pensé solliciter des commentaires sur la meilleure façon de décrire la différence.

ScottKoon
la source
2
Par «délégués», voulez-vous dire des types de délégués ou des délégués anonymes? Ils sont également différents.
Chris Ammerman
1
pourquoi les gens rendent sa question si compliquée? Répondez simplement à ce qu'est un délégué et à ce qu'est un lambda. Donnez autant d'explications que possible et laissez-le choisir ce qui lui convient.
Imir Hoxha

Réponses:

96

Ce sont en fait deux choses très différentes. «Delegate» est en fait le nom d'une variable qui contient une référence à une méthode ou à un lambda, et un lambda est une méthode sans nom permanent.

Les lambdas ressemblent beaucoup aux autres méthodes, à l'exception de quelques différences subtiles.

  1. Une méthode normale est définie dans une "instruction" et liée à un nom permanent, alors qu'un lambda est défini "à la volée" dans une "expression" et n'a pas de nom permanent.
  2. Certains lambdas peuvent être utilisés avec des arborescences d'expressions .NET, contrairement aux méthodes.

Un délégué est défini comme ceci:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

Une variable de type BinaryIntOp peut avoir une méthode ou un labmda qui lui est assigné, tant que la signature est la même: deux arguments Int32 et un retour Int32.

Un lambda peut être défini comme ceci:

BinaryIntOp sumOfSquares = (a, b) => a*a + b*b;

Une autre chose à noter est que bien que les types génériques Func et Action soient souvent considérés comme des «types lambda», ils sont comme tous les autres délégués. La bonne chose à leur sujet est qu'ils définissent essentiellement un nom pour tout type de délégué dont vous pourriez avoir besoin (jusqu'à 4 paramètres, bien que vous puissiez certainement en ajouter d'autres). Donc, si vous utilisez une grande variété de types de délégués, mais aucun plus d'une fois, vous pouvez éviter d'encombrer votre code avec des déclarations de délégués en utilisant Func et Action.

Voici une illustration de la façon dont Func et Action ne sont "pas seulement pour les lambdas":

Int32 DiffOfSquares(Int32 x, Int32 y)
{
  return x*x - y*y;
}

Func<Int32, Int32, Int32> funcPtr = DiffOfSquares;

Une autre chose utile à savoir est que les types délégués (pas les méthodes elles-mêmes) avec la même signature mais des noms différents ne seront pas transtypés implicitement les uns vers les autres. Cela inclut les délégués Func et Action. Cependant, si la signature est identique, vous pouvez effectuer un cast explicitement entre elles.

Aller plus loin ... En C #, les fonctions sont flexibles, avec l'utilisation de lambdas et de délégués. Mais C # n'a pas de "fonctions de première classe". Vous pouvez utiliser le nom d'une fonction attribué à une variable déléguée pour créer essentiellement un objet représentant cette fonction. Mais c'est vraiment une astuce de compilateur. Si vous démarrez une instruction en écrivant le nom de la fonction suivi d'un point (c'est-à-dire essayez de faire un accès membre sur la fonction elle-même), vous constaterez qu'il n'y a aucun membre à référencer. Pas même ceux d'Object. Cela empêche le programmeur de faire des choses utiles (et potentiellement dangereuses bien sûr) telles que l'ajout de méthodes d'extension qui peuvent être appelées sur n'importe quelle fonction. Le mieux que vous puissiez faire est d'étendre la classe Delegate elle-même, ce qui est sûrement également utile, mais pas autant.

Mise à jour: voir également la réponse de Karg illustrant la différence entre les délégués anonymes et les méthodes et lambdas.

Mise à jour 2: James Hart fait une remarque importante, bien que très technique, que les lambdas et les délégués ne sont pas des entités .NET (c'est-à-dire que le CLR n'a pas de concept de délégué ou de lambda), mais plutôt des constructions de framework et de langage.

Chris Ammerman
la source
Bonne explication. Bien que je pense que vous voulez dire "fonctions de première classe", pas "objets de première classe". :)
ibz
1
Tu as raison. J'ai eu la phrase structurée différemment pendant l'écriture ("les fonctions C # ne sont pas réellement des objets de première classe") et j'ai oublié de changer cela. Merci!
Chris Ammerman le
Une méthode normale est définie dans une "instruction" Une instruction est une action dans la séquence d'un programme impératif, éventuellement basée sur une expression. Une définition de méthode n'est-elle pas une structure grammaticale différente? La définition de méthode n'est pas répertoriée dans docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp
Max Barraclough
32

La question est un peu ambiguë, ce qui explique la grande disparité des réponses que vous obtenez.

Vous avez en fait demandé quelle était la différence entre les lambdas et les délégués dans le framework .NET; cela pourrait être l'une des nombreuses choses. Demandez-vous:

  • Quelle est la différence entre les expressions lambda et les délégués anonymes dans le langage C # (ou VB.NET)?

  • Quelle est la différence entre les objets System.Linq.Expressions.LambdaExpression et les objets System.Delegate dans .NET 3.5?

  • Ou quelque chose quelque part entre ou autour de ces extrêmes?

Certaines personnes semblent essayer de vous donner la réponse à la question «Quelle est la différence entre les expressions C # Lambda et .NET System.Delegate?», Ce qui n'a pas beaucoup de sens.

Le framework .NET ne comprend pas en soi les concepts de délégués anonymes, d'expressions lambda ou de fermetures - tout cela est défini par les spécifications du langage. Pensez à la façon dont le compilateur C # traduit la définition d'une méthode anonyme en une méthode sur une classe générée avec des variables membres pour maintenir l'état de fermeture; à .NET, il n'y a rien d'anonyme sur le délégué; c'est juste anonyme pour le programmeur C # qui l'écrit. C'est également vrai pour une expression lambda affectée à un type de délégué.

Qu'est - ce que .NET DOES comprendre est l'idée d'un délégué - un type qui décrit une signature de méthode, les instances qui correspondent soit à des appels liés à des méthodes de spécifiques sur des objets spécifiques, ou des appels non liés à une méthode particulière sur un type particulier qui peut être invoqué à l' encontre tout objet de ce type, où ladite méthode adhère à ladite signature. Ces types héritent tous de System.Delegate.

.NET 3.5 introduit également l'espace de noms System.Linq.Expressions, qui contient des classes pour décrire des expressions de code - et qui peut donc également représenter des appels liés ou non à des méthodes sur des types ou des objets particuliers. Les instances LambdaExpression peuvent ensuite être compilées en délégués réels (grâce à quoi une méthode dynamique basée sur la structure de l'expression est codée et un pointeur de délégué vers elle est renvoyé).

En C #, vous pouvez produire des instances de types System.Expressions.Expression en affectant une expression lambda à une variable dudit type, qui produira le code approprié pour construire l'expression au moment de l'exécution.

Bien sûr, si vous étiez demandez quelle est la différence entre les expressions lambda et les méthodes anonymes en C #, après tout, alors tout cela est à peu près irelevant, et dans ce cas , la principale différence est la brièveté, qui se penche vers les délégués anonymes lorsque vous n » t se soucient des paramètres et ne prévoyez pas de retourner une valeur, et vers les lambdas lorsque vous voulez des paramètres inférenciés de type et des types de retour.

Et les expressions lambda prennent en charge la génération d'expressions.

James Hart
la source
3
Bonne info! Vous m'avez inspiré pour allumer le réflecteur et regarder l'IL. Je ne savais pas que les lambdas aboutissaient à des classes générées, mais c'est parfaitement logique maintenant que j'y pense.
Chris Ammerman
20

Une différence est qu'un délégué anonyme peut omettre des paramètres tandis qu'un lambda doit correspondre à la signature exacte. Donné:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

vous pouvez l'appeler des quatre manières suivantes (notez que la deuxième ligne a un délégué anonyme qui n'a aucun paramètre):

Test(delegate(int i) { return String.Empty; });
Test(delegate { return String.Empty; });
Test(i => String.Empty);
Test(D);

private string D(int i)
{
    return String.Empty;
}

Vous ne pouvez pas transmettre une expression lambda qui n'a pas de paramètres ou une méthode qui n'a pas de paramètres. Ceux-ci ne sont pas autorisés:

Test(() => String.Empty); //Not allowed, lambda must match signature
Test(D2); //Not allowed, method must match signature

private string D2()
{
    return String.Empty;
}
Karg
la source
13

Les délégués sont équivalents aux pointeurs de fonction / pointeurs de méthode / rappels (faites votre choix), et les lambdas sont des fonctions anonymes à peu près simplifiées. Du moins c'est ce que je dis aux gens.

Dan Shield
la source
Exactement! Il n'y a pas de différence". Ce sont deux choses intrinsèquement différentes.
ibz
3

Je n'ai pas une tonne d'expérience avec cela, mais la façon dont je le décrirais est qu'un délégué est un wrapper autour de n'importe quelle fonction, alors qu'une expression lambda est elle-même une fonction anonyme.

échecs
la source
3

Un délégué est toujours essentiellement un pointeur de fonction. Un lambda peut se transformer en délégué, mais il peut également se transformer en une arborescence d'expression LINQ. Par exemple,

Func<int, int> f = x => x + 1;
Expression<Func<int, int>> exprTree = x => x + 1;

La première ligne produit un délégué, tandis que la seconde produit une arborescence d'expression.

Curt Hagenlocher
la source
2
C'est vrai, mais la différence entre eux est qu'il s'agit de deux concepts complètement différents . C'est comme comparer des pommes et des oranges. Voir la réponse de Dan Shield.
ibz
2

Les lambdas sont simplement du sucre syntaxique sur un délégué. Le compilateur finit par convertir les lambdas en délégués.

Ce sont les mêmes, je crois:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};
Gilligan
la source
2
aucun de ces exemples ne se compile. Même si vous modifiez le nom de l'instance de Delegatefrom 'delegate', qui est un mot-clé.
Steve Cooper le
2

Un délégué est une signature de fonction; quelque chose comme

delegate string MyDelegate(int param1);

Le délégué n'implémente pas de corps.

Le lambda est un appel de fonction qui correspond à la signature du délégué. Pour le délégué ci-dessus, vous pouvez utiliser l'un des;

(int i) => i.ToString();
(int i) => "ignored i";
(int i) => "Step " + i.ToString() + " of 10";

Le Delegatetype est mal nommé, cependant; la création d'un objet de type Delegatecrée en fait une variable qui peut contenir des fonctions - qu'il s'agisse de lambdas, de méthodes statiques ou de méthodes de classe.

Steve Cooper
la source
Lorsque vous créez une variable de type MyDelegate, ce n'est pas vraiment son type d'exécution. Le type d'exécution est Delegate. Il y a des astuces de compilateur impliquées dans la façon dont les délégués, les lambdas et les arbres d'expression se compilent, ce qui, je pense, fait que le code implique des choses qui ne sont pas vraies.
Chris Ammerman
2

Un délégué est une référence à une méthode avec une liste de paramètres et un type de retour particuliers. Il peut inclure ou non un objet.

Une expression lambda est une forme de fonction anonyme.

Peter Ritchie
la source
2

Un délégué est une file d'attente de pointeurs de fonction, l'appel d'un délégué peut invoquer plusieurs méthodes. Un lambda est essentiellement une déclaration de méthode anonyme qui peut être interprétée différemment par le compilateur, en fonction du contexte dans lequel il est utilisé.

Vous pouvez obtenir un délégué qui pointe vers l'expression lambda en tant que méthode en le convertissant en délégué, ou si vous le transmettez en tant que paramètre à une méthode qui attend un type de délégué spécifique, le compilateur le castera pour vous. En l'utilisant à l'intérieur d'une instruction LINQ, le lambda sera traduit par le compilateur dans une arborescence d'expression au lieu d'un simple délégué.

La différence est vraiment qu'un lambda est un moyen laconique de définir une méthode à l'intérieur d'une autre expression, tandis qu'un délégué est un type d'objet réel.

justin.m.chase
la source
2

Il est assez clair que la question était censée être "quelle est la différence entre les lambdas et les délégués anonymes ?" Sur toutes les réponses ici, une seule personne a bien compris - la principale différence est que les lambdas peuvent être utilisées pour créer des arbres d'expression ainsi que des délégués.

Vous pouvez en savoir plus sur MSDN: http://msdn.microsoft.com/en-us/library/bb397687.aspx

Philip Beber
la source
1

Les délégués ne sont en réalité que du typage structurel pour les fonctions. Vous pouvez faire la même chose avec un typage nominal et l'implémentation d'une classe anonyme qui implémente une interface ou une classe abstraite, mais cela finit par être beaucoup de code lorsqu'une seule fonction est nécessaire.

Lambda vient de l'idée du calcul lambda de l'église d'Alonzo dans les années 1930. C'est une manière anonyme de créer des fonctions. Ils deviennent particulièrement utiles pour composer des fonctions

Ainsi, alors que certains pourraient dire que lambda est du sucre syntaxique pour les délégués, je dirais que les délégués sont un pont pour faciliter les gens dans les lambdas en c #.

Steve g
la source
1

Quelques basiques ici. "Delegate" est en fait le nom d'une variable qui contient une référence à une méthode ou à un lambda

Ceci est une méthode anonyme -

(string testString) => { Console.WriteLine(testString); };

Comme la méthode anonyme n'a pas de nom, nous avons besoin d'un délégué dans lequel nous pouvons attribuer ces deux méthodes ou expressions. Pour Ex.

delegate void PrintTestString(string testString); // declare a delegate

PrintTestString print = (string testString) => { Console.WriteLine(testString); }; 
print();

Idem avec l'expression lambda. Habituellement, nous avons besoin d'un délégué pour les utiliser

s => s.Age > someValue && s.Age < someValue    // will return true/false

Nous pouvons utiliser un délégué func pour utiliser cette expression.

Func< Student,bool> checkStudentAge = s => s.Age > someValue && s.Age < someValue ;

bool result = checkStudentAge ( Student Object);
Yogesh Prajapati
la source
0

Les lambdas sont des versions simplifiées des délégués. Ils ont certaines des propriétés d'une fermeture comme les délégués anonymes, mais vous permettent également d'utiliser le typage implicite. Un lambda comme celui-ci:

something.Sort((x, y) => return x.CompareTo(y));

est beaucoup plus concis que ce que vous pouvez faire avec un délégué:

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}
Michael Meadows
la source
Vous voulez dire que les lambdas sont comme des méthodes anonymes simplifiées (pas déléguées). Comme les méthodes (anonymes ou non), elles peuvent être affectées à une variable déléguée.
Lucas
0

Voici un exemple que j'ai mis un certain temps sur mon blog boiteux. Supposons que vous vouliez mettre à jour une étiquette à partir d'un thread de travail. J'ai 4 exemples de mise à jour de cette étiquette de 1 à 50 en utilisant des délégués, des délégués anon et 2 types de lambdas.

 private void button2_Click(object sender, EventArgs e) 
     { 
         BackgroundWorker worker = new BackgroundWorker(); 
         worker.DoWork += new DoWorkEventHandler(worker_DoWork); 
         worker.RunWorkerAsync(); 
     } 

     private delegate void UpdateProgDelegate(int count); 
     private void UpdateText(int count) 
     { 
         if (this.lblTest.InvokeRequired) 
         { 
             UpdateProgDelegate updateCallBack = new UpdateProgDelegate(UpdateText); 
             this.Invoke(updateCallBack, new object[] { count }); 
         } 
         else 
         { 
             lblTest.Text = count.ToString(); 
         } 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     {   
         /* Old Skool delegate usage.  See above for delegate and method definitions */ 
         for (int i = 0; i < 50; i++) 
         { 
             UpdateText(i); 
             Thread.Sleep(50); 
         } 

         // Anonymous Method 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((MethodInvoker)(delegate() 
             { 
                 lblTest.Text = i.ToString(); 
             })); 
             Thread.Sleep(50); 
         } 

         /* Lambda using the new Func delegate. This lets us take in an int and 
          * return a string.  The last parameter is the return type. so 
          * So Func<int, string, double> would take in an int and a string 
          * and return a double.  count is our int parameter.*/ 
         Func<int, string> UpdateProgress = (count) => lblTest.Text = count.ToString(); 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke(UpdateProgress, i); 
             Thread.Sleep(50); 
         } 

         /* Finally we have a totally inline Lambda using the Action delegate 
          * Action is more or less the same as Func but it returns void. We could 
          * use it with parameters if we wanted to like this: 
          * Action<string> UpdateProgress = (count) => lblT…*/ 
         for (int i = 0; i < 50; i++) 
         { 
             lblTest.Invoke((Action)(() => lblTest.Text = i.ToString())); 
             Thread.Sleep(50); 
         } 
     }
Echostorm
la source
0

Je suppose que votre question concerne c # et non .NET, à cause de l'ambiguïté de votre question, car .NET n'obtient pas seul - c'est-à-dire sans c # - la compréhension des délégués et des expressions lambda.

Un délégué ( normal , par opposition aux délégués dits génériques , cf plus tard) doit être vu comme une sorte de c ++ typedefd'un type pointeur de fonction, par exemple en c ++:

R (*thefunctionpointer) ( T ) ;

typedef est le type thefunctionpointerqui est le type de pointeurs vers une fonction prenant un objet de type Tet renvoyant un objet de type R. Vous l'utiliseriez comme ceci:

thefunctionpointer = &thefunction ;
R r = (*thefunctionpointer) ( t ) ; // where t is of type T

thefunctionserait une fonction prenant un Tet renvoyant un R.

En c # vous iriez pour

delegate R thedelegate( T t ) ; // and yes, here the identifier t is needed

et vous l'utiliseriez comme ceci:

thedelegate thedel = thefunction ;
R r = thedel ( t ) ; // where t is of type T

thefunctionserait une fonction prenant un Tet renvoyant un R. Ceci est pour les délégués, dits délégués normaux.

Maintenant, vous avez aussi des délégués génériques en c #, qui sont des délégués qui sont génériques, c'est -à- dire qui sont pour ainsi dire "modelés", utilisant ainsi une expression c ++. Ils sont définis comme ceci:

public delegate TResult Func<in T, out TResult>(T arg);

Et vous pouvez les utiliser comme ceci:

Func<double, double> thefunctor = thefunction2; // call it a functor because it is
                                                // really as a functor that you should
                                                // "see" it
double y = thefunctor(2.0);

thefunction2est une fonction prenant comme argument et retournant un double.

Imaginez maintenant qu'au lieu de cela, thefunction2je voudrais utiliser une "fonction" qui n'est nulle part définie pour l'instant, par une instruction, et que je n'utiliserai jamais plus tard. Alors c # nous permet d'utiliser l' expression de cette fonction. Par l' expression je veux dire la « mathématique » (ou fonctionnelle, de coller aux programmes) l' expression de celui - ci, par exemple: à un j'associer le . En maths, vous écrivez ceci en utilisant le symbole latex "\ mapsto" . En c # la notation fonctionnelle a été emprunté: . Par exemple :double xdouble x*x=>

Func<double, double> thefunctor = ( (double x) => x * x ); // outer brackets are not
                                                           // mandatory

(double x) => x * xest une expression . Ce n'est pas un type, contrairement aux délégués (génériques ou non).

Moralité ? À la fin, qu'est-ce qu'un délégué (resp. Délégué générique), sinon un type pointeur de fonction (resp. Type pointeur de fonction enveloppé + intelligent + générique), hein? Autre chose ! Voyez ceci et cela .

ujsgeyrr1f0d0d0r0h1h0j0j_juj
la source
-1

Eh bien, la version vraiment simplifiée est qu'un lambda n'est qu'un raccourci pour une fonction anonyme. Un délégué peut faire beaucoup plus que de simples fonctions anonymes: des choses comme des événements, des appels asynchrones et plusieurs chaînes de méthodes.

Joël Coehoorn
la source
1
lambdas peuvent être utilisés comme gestionnaires d'événements; button.Click + = (expéditeur, eventArgs) => {MessageBox.Show ("Click"); } et appelé de manière asynchrone nouveau System.Threading.Thread (() => Console.Write ("Exécuté sur un thread")). Start ();
Steve Cooper