¿Cuál es la diferencia entre lambdas y delegados en .NET Framework?

86

Me hacen mucho esta pregunta y pensé en solicitar alguna opinión sobre cómo describir mejor la diferencia.

ScottKoon
fuente
2
Por "delegados", ¿te refieres a tipos de delegados o delegados anónimos? También son diferentes.
Chris Ammerman
1
¿Por qué la gente hace tan complicada su pregunta? Simplemente responda qué es un delegado y qué es una lambda. Dé todas las explicaciones posibles y déjele elegir lo que sea apropiado para él.
Imir Hoxha

Respuestas:

96

En realidad, son dos cosas muy diferentes. "Delegado" es en realidad el nombre de una variable que contiene una referencia a un método o una lambda, y una lambda es un método sin un nombre permanente.

Las lambdas son muy parecidas a otros métodos, excepto por un par de diferencias sutiles.

  1. Un método normal se define en una "declaración" y está vinculado a un nombre permanente, mientras que una lambda se define "sobre la marcha" en una "expresión" y no tiene un nombre permanente.
  2. Algunas lambdas se pueden usar con árboles de expresión .NET, mientras que los métodos no.

Un delegado se define así:

delegate Int32 BinaryIntOp(Int32 x, Int32 y);

Una variable de tipo BinaryIntOp puede tener asignado un método o un labmda, siempre que la firma sea la misma: dos argumentos Int32 y un retorno Int32.

Una lambda podría definirse así:

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

Otra cosa a tener en cuenta es que, aunque los tipos genéricos de Func y Action a menudo se consideran "tipos lambda", son como cualquier otro delegado. Lo bueno de ellos es que esencialmente definen un nombre para cualquier tipo de delegado que pueda necesitar (hasta 4 parámetros, aunque ciertamente puede agregar más de los suyos). Entonces, si está utilizando una amplia variedad de tipos de delegados, pero ninguno más de una vez, puede evitar abarrotar su código con declaraciones de delegado utilizando Func y Action.

Aquí hay una ilustración de cómo Func y Action "no son solo para lambdas":

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

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

Otra cosa útil que debe saber es que los tipos de delegado (no los métodos en sí mismos) con la misma firma pero con nombres diferentes no se transmitirán implícitamente entre sí. Esto incluye a los delegados de Func y Action. Sin embargo, si la firma es idéntica, puede emitir explícitamente entre ellos.

Haciendo un esfuerzo adicional .... En C # las funciones son flexibles, con el uso de lambdas y delegados. Pero C # no tiene "funciones de primera clase". Puede usar el nombre de una función asignado a una variable delegada para crear esencialmente un objeto que represente esa función. Pero es realmente un truco de compilación. Si comienza una declaración escribiendo el nombre de la función seguido de un punto (es decir, intenta hacer acceso a los miembros en la función en sí), encontrará que no hay miembros para hacer referencia. Ni siquiera los de Object. Esto evita que el programador haga cosas útiles (y potencialmente peligrosas, por supuesto) como agregar métodos de extensión que se pueden llamar en cualquier función. Lo mejor que puede hacer es extender la clase Delegate en sí, que seguramente también es útil, pero no tanto.

Actualización: vea también la respuesta de Karg que ilustra la diferencia entre delegados anónimos versus métodos y lambdas.

Actualización 2: James Hart hace una nota importante, aunque muy técnica, de que las lambdas y los delegados no son entidades .NET (es decir, CLR no tiene el concepto de delegado o lambda), sino que son construcciones de marco y lenguaje.

Chris Ammerman
fuente
Buena explicación. Aunque creo que te refieres a "funciones de primera clase", no a "objetos de primera clase". :)
ibz
1
Tienes razón. Tenía la oración estructurada de manera diferente durante la escritura ("Las funciones de C # no son en realidad objetos de primera clase") y olvidé cambiar eso. ¡Gracias!
Chris Ammerman
Un método normal se define en una "declaración". Una declaración es una acción en la secuencia de un programa imperativo, posiblemente basada en una expresión. ¿No es una definición de método una estructura gramatical diferente? La definición del método no aparece en docs.microsoft.com/en-us/dotnet/csharp/tour-of-csharp/…
Max Barraclough
32

La pregunta es un poco ambigua, lo que explica la gran disparidad en las respuestas que está obteniendo.

De hecho, preguntó cuál es la diferencia entre lambdas y delegados en el marco .NET; esa podría ser una de varias cosas. Estás preguntando:

  • ¿Cuál es la diferencia entre las expresiones lambda y los delegados anónimos en el lenguaje C # (o VB.NET)?

  • ¿Cuál es la diferencia entre los objetos System.Linq.Expressions.LambdaExpression y los objetos System.Delegate en .NET 3.5?

  • ¿O algo en algún lugar entre esos extremos o alrededor de ellos?

Algunas personas parecen estar tratando de darle la respuesta a la pregunta '¿cuál es la diferencia entre las expresiones C # Lambda y .NET System.Delegate?', Lo cual no tiene mucho sentido.

El marco .NET en sí mismo no comprende los conceptos de delegados anónimos, expresiones lambda o cierres; todas esas cosas están definidas por las especificaciones del lenguaje. Piense en cómo el compilador de C # traduce la definición de un método anónimo en un método en una clase generada con variables miembro para mantener el estado de cierre; para .NET, no hay nada anónimo sobre el delegado; es simplemente anónimo para el programador de C # que lo escribe. Eso es igualmente cierto para una expresión lambda asignada a un tipo de delegado.

Lo que .NET entiende es la idea de un delegado: un tipo que describe la firma de un método, cuyas instancias representan llamadas vinculadas a métodos específicos en objetos específicos o llamadas no vinculadas a un método particular en un tipo particular que se puede invocar contra cualquier objeto de ese tipo, donde dicho método se adhiera a dicha firma. Todos estos tipos heredan de System.Delegate.

.NET 3.5 también presenta el espacio de nombres System.Linq.Expressions, que contiene clases para describir expresiones de código y que, por lo tanto, también puede representar llamadas vinculadas o no vinculadas a métodos en tipos u objetos particulares. Las instancias de LambdaExpression se pueden compilar en delegados reales (mediante el cual se codifica un método dinámico basado en la estructura de la expresión y se devuelve un puntero de delegado).

En C # puede producir instancias de los tipos System.Expressions.Expression asignando una expresión lambda a una variable de dicho tipo, que producirá el código apropiado para construir la expresión en tiempo de ejecución.

Por supuesto, si estabas preguntando cuál es la diferencia entre las expresiones lambda y métodos anónimos en C #, después de todo, entonces todo esto es más o menos irelevant, y en ese caso la diferencia principal es la brevedad, que se inclina hacia los delegados anónimos cuando usted don' No te preocupes por los parámetros y no planeas devolver un valor, y hacia lambdas cuando quieras escribir parámetros inferenciados y tipos de retorno.

Y las expresiones lambda admiten la generación de expresiones.

James Hart
fuente
3
¡Excelente información! Me inspiraste a encender el reflector y mirar el IL. No sabía que las lambdas resultaban en clases generadas, pero tiene mucho sentido ahora que lo pienso.
Chris Ammerman
20

Una diferencia es que un delegado anónimo puede omitir parámetros, mientras que una lambda debe coincidir con la firma exacta. Dado:

public delegate string TestDelegate(int i);

public void Test(TestDelegate d)
{}

puede llamarlo de las siguientes cuatro formas (tenga en cuenta que la segunda línea tiene un delegado anónimo que no tiene ningún parámetro):

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;
}

No puede pasar una expresión lambda que no tenga parámetros o un método que no tenga parámetros. Estos no están permitidos:

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

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

Los delegados son equivalentes a punteros de función / punteros de método / devoluciones de llamada (elija), y las lambdas son funciones anónimas bastante simplificadas. Al menos eso es lo que le digo a la gente.

Dan Shield
fuente
¡Exactamente! No hay diferencia". Son dos cosas inherentemente diferentes.
ibz
3

No tengo mucha experiencia con esto, pero la forma en que lo describiría es que un delegado es un envoltorio de cualquier función, mientras que una expresión lambda es en sí misma una función anónima.

ajedrez
fuente
3

Un delegado es siempre básicamente un puntero de función. Una lambda puede convertirse en un delegado, pero también puede convertirse en un árbol de expresión LINQ. Por ejemplo,

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

La primera línea produce un delegado, mientras que la segunda produce un árbol de expresión.

Curt Hagenlocher
fuente
2
Eso es cierto, pero la diferencia entre ellos es que son dos conceptos completamente diferentes . Es como comparar manzanas y naranjas. Vea la respuesta de Dan Shield.
ibz
2

lambdas son simplemente azúcar sintáctico en un delegado. El compilador termina convirtiendo lambdas en delegados.

Estos son los mismos, creo:

Delegate delegate = x => "hi!";
Delegate delegate = delegate(object x) { return "hi";};
Gilligan
fuente
2
ninguno de estos ejemplos se compila. Incluso si cambia el nombre de la instancia de Delegatede 'delegado', que es una palabra clave.
Steve Cooper
2

Un delegado es una firma de función; algo como

delegate string MyDelegate(int param1);

El delegado no implementa un cuerpo.

La lambda es una llamada a función que coincide con la firma del delegado. Para el delegado anterior, puede usar cualquiera de;

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

Sin Delegateembargo, el tipo está mal nombrado; la creación de un objeto de tipo en Delegaterealidad crea una variable que puede contener funciones, ya sean lambdas, métodos estáticos o métodos de clase.

Steve Cooper
fuente
Cuando crea una variable de tipo MyDelegate, ese no es realmente el tipo de tiempo de ejecución. El tipo de tiempo de ejecución es Delegado. Hay trucos de compilación involucrados en cómo se compilan los delegados, lambdas y árboles de expresión, lo que creo que porque el código implica cosas que no son ciertas.
Chris Ammerman
2

Un delegado es una referencia a un método con una lista de parámetros y un tipo de retorno particulares. Puede incluir o no un objeto.

Una expresión lambda es una forma de función anónima.

Peter Ritchie
fuente
2

Un delegado es una cola de punteros de función, invocar a un delegado puede invocar varios métodos. Una lambda es esencialmente una declaración de método anónimo que el compilador puede interpretar de manera diferente, dependiendo del contexto en el que se use.

Puede obtener un delegado que apunte a la expresión lambda como un método al convertirlo en un delegado, o si lo pasa como un parámetro a un método que espera un tipo de delegado específico, el compilador lo emitirá por usted. Al usarlo dentro de una declaración LINQ, el compilador traducirá el lambda en un árbol de expresión en lugar de simplemente un delegado.

La diferencia realmente es que una lambda es una forma concisa de definir un método dentro de otra expresión, mientras que un delegado es un tipo de objeto real.

Justin M. Chase
fuente
2

Está bastante claro que la pregunta debía ser "¿cuál es la diferencia entre lambdas y delegados anónimos ?" De todas las respuestas aquí, solo una persona lo hizo bien: la principal diferencia es que las lambdas se pueden usar para crear árboles de expresión y delegados.

Puede leer más en MSDN: http://msdn.microsoft.com/en-us/library/bb397687.aspx

Philip Beber
fuente
1

Los delegados son en realidad mecanografía estructural para funciones. Podría hacer lo mismo con la escritura nominal e implementando una clase anónima que implementa una interfaz o clase abstracta, pero eso termina siendo mucho código cuando solo se necesita una función.

Lambda proviene de la idea del cálculo lambda de Alonzo Church en la década de 1930. Es una forma anónima de crear funciones. Se vuelven especialmente útiles para componer funciones.

Entonces, aunque algunos podrían decir que lambda es azúcar sintáctico para los delegados, yo diría que los delegados son un puente para facilitar a las personas el uso de lambdas en c #.

Steve g
fuente
1

Algunos básicos aquí. "Delegado" es en realidad el nombre de una variable que contiene una referencia a un método o una lambda.

Este es un método anónimo:

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

Como el método anónimo no tiene ningún nombre, necesitamos un delegado en el que podamos asignar ambos métodos o expresiones. Por ej.

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

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

Lo mismo con la expresión lambda. Por lo general, necesitamos un delegado para usarlos.

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

Podemos usar un delegado func para usar esta expresión.

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

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

Las lambdas son versiones simplificadas de delegados. Tienen algunas de las propiedades de un cierre como los delegados anónimos, pero también le permiten usar la escritura implícita. Una lambda como esta:

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

es mucho más conciso de lo que puede hacer con un delegado:

something.Sort(sortMethod);
...

private int sortMethod(SomeType one, SomeType two)
{
    one.CompareTo(two)
}
Michael Meadows
fuente
Quiere decir que las lambdas son como métodos anónimos simplificados (no delegados). Al igual que los métodos (anónimos o no), se pueden asignar a una variable delegada.
Lucas
0

Aquí hay un ejemplo que puse un rato en mi aburrido blog. Supongamos que desea actualizar una etiqueta de un hilo de trabajo. Tengo 4 ejemplos de cómo actualizar esa etiqueta de 1 a 50 usando delegados, delegados anon y 2 tipos 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
fuente
0

Supongo que su pregunta concierne a c # y no a .NET, debido a la ambigüedad de su pregunta, ya que .NET no se queda solo, es decir, sin c #, comprensión de delegados y expresiones lambda.

Un delegado ( normal , en oposición a los llamados delegados genéricos , cf más adelante) debe verse como una especie de c ++ typedefde un tipo de puntero de función, por ejemplo en c ++:

R (*thefunctionpointer) ( T ) ;

typedef es el tipo thefunctionpointerque es el tipo de punteros a una función que toma un objeto de tipo Ty devuelve un objeto de tipo R. Lo usarías así:

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

donde thefunctionsería una función tomando un Ty devolviendo un R.

En c # irías por

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

y lo usarías así:

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

donde thefunctionsería una función tomando un Ty devolviendo un R. Esto es para los delegados, los llamados delegados normales.

Ahora, también tiene delegados genéricos en c #, que son delegados que son genéricos, es decir , que tienen "plantillas", por así decirlo, utilizando así una expresión c ++. Se definen así:

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

Y puedes usarlos así:

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);

donde thefunction2es una función que toma como argumento y devuelve un double.

Ahora imagina que, en lugar de, thefunction2me gustaría usar una "función" que no está definida en ninguna parte por ahora, mediante una declaración, y que nunca usaré más adelante. Entonces c # nos permite usar la expresión de esta función. Por expresión me refiero a la expresión "matemática" (o funcional, para ceñirse a los programas) de la misma, por ejemplo: a un double xyo asociaré el double x*x. En matemáticas, escribe esto usando el símbolo de látex "\ mapsto" . En C # la notación funcional se ha prestado: =>. Por ejemplo :

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

(double x) => x * xes una expresión . No es un tipo, mientras que los delegados (genéricos o no) lo son.

Moralidad? Al final, ¿qué es un delegado (resp. Delegado genérico), si no un tipo de puntero de función (resp. Envuelto + tipo de puntero de función inteligente + genérico), eh? Algo más ! Vea esto y aquello .

ujsgeyrr1f0d0d0r0h1h0j0j_juj
fuente
-1

Bueno, la versión realmente simplificada es que lambda es una abreviatura de una función anónima. Un delegado puede hacer mucho más que funciones anónimas: cosas como eventos, llamadas asincrónicas y múltiples cadenas de métodos.

Joel Coehoorn
fuente
1
las lambdas se pueden utilizar como controladores de eventos; button.Click + = (sender, eventArgs) => {MessageBox.Show ("Click"); } y llamó asincrónicamente nuevo System.Threading.Thread (() => Console.Write ("Ejecutado en un hilo")). Start ();
Steve Cooper