C # expresiones lambda: ¿por qué debería usarlas?

310

Leí rápidamente la documentación de Microsoft Lambda Expression .

Sin embargo, este tipo de ejemplo me ha ayudado a comprender mejor:

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

Aún así, no entiendo por qué es una innovación. Es solo un método que muere cuando termina la "variable del método", ¿verdad? ¿Por qué debería usar esto en lugar de un método real?

Patrick Desjardins
fuente
3
Para aquellos de ustedes que vienen a esta página y no saben qué delegatehay en C #, les recomiendo leer esto antes de leer el resto de esta página: stackoverflow.com/questions/2082615/…
Kolob Canyon

Respuestas:

282

Las expresiones lambda son una sintaxis más simple para delegados anónimos y se pueden usar en todas partes donde se puede usar un delegado anónimo. Sin embargo, lo contrario no es cierto; Las expresiones lambda se pueden convertir en árboles de expresión, lo que permite mucha magia como LINQ to SQL.

El siguiente es un ejemplo de una expresión LINQ to Objects que utiliza delegados anónimos y luego expresiones lambda para mostrar qué tan fáciles de ver son:

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

Las expresiones Lambda y los delegados anónimos tienen una ventaja sobre escribir una función separada: implementan cierres que pueden permitirle pasar el estado local a la función sin agregar parámetros a la función o crear objetos de un solo uso.

Los árboles de expresión son una nueva característica muy poderosa de C # 3.0 que permite que una API mire la estructura de una expresión en lugar de simplemente obtener una referencia a un método que se puede ejecutar. Una API solo tiene que convertir un parámetro delegado en un Expression<T>parámetro y el compilador generará un árbol de expresión a partir de una lambda en lugar de un delegado anónimo:

void Example(Predicate<int> aDelegate);

llamado como:

Example(x => x > 5);

se convierte en:

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

A este último se le pasará una representación del árbol de sintaxis abstracta que describe la expresión x > 5. LINQ to SQL se basa en este comportamiento para poder convertir expresiones C # en las expresiones SQL deseadas para el filtrado / pedido / etc. en el lado del servidor.

Neil Williams
fuente
1
Sin cierres, puede usar métodos estáticos como devoluciones de llamada, pero aún tiene que definir esos métodos en alguna clase, lo que aumenta casi con certeza el alcance de dicho método más allá del uso previsto.
DK.
10
FWIW, puede tener cierres con un delegado anónimo, por lo que no necesita estrictamente lambdas para eso. Las lambdas son eminentemente más legibles que los delegados anónimos, sin los cuales usar Linq haría que sus ojos sangraran.
Benjol
138

Las funciones y expresiones anónimas son útiles para métodos únicos que no se benefician del trabajo adicional requerido para crear un método completo.

Considere este ejemplo:

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

versus

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

Estos son funcionalmente equivalentes.

Joseph Daigle
fuente
8
¿Cómo se habría definido el método Find () para manejar esta expresión lambda?
Patrick Desjardins el
3
Predicar <T> es lo que espera el método Find.
Darren Kopp
1
Como mi expresión lambda coincide con el contrato para Predicate <T>, el método Find () lo acepta.
Joseph Daigle
¿quiso decir "string person = people.Find (persons => persons.Contains (" Joe "));"
Gern Blanston el
55
@FKCoder, no, no lo hace, aunque podría haber sido más claro si hubiera dicho "string person = people.Find (p => p.Contains (" Joe "));"
Benjol
84

Los encontré útiles en una situación en la que quería declarar un controlador para algún evento de control, usando otro control. Para hacerlo normalmente, tendría que almacenar las referencias de los controles en los campos de la clase para poder usarlos en un método diferente al que se crearon.

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

gracias a las expresiones lambda puedes usarlo así:

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

Más fácil.

agnieszka
fuente
En el primer ejemplo, ¿por qué no enviar el remitente y obtener el valor?
Andrew
@ Andrew: en este sencillo ejemplo no es necesario usar el remitente, porque solo hay un componente en cuestión y el uso del campo guarda directamente un reparto, lo que mejora la claridad. En un escenario del mundo real, personalmente preferiría usar también al remitente. Por lo general, uso un controlador de eventos para varios eventos, si es posible, por lo que tengo que identificar al remitente real.
Chris Tophski el
35

Lambda ha limpiado la sintaxis de delegado anónimo de C # 2.0 ... por ejemplo

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

Se hizo en C # 2.0 de esta manera:

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

Funcionalmente, hacen exactamente lo mismo, es una sintaxis mucho más concisa.

FlySwat
fuente
3
No son exactamente lo mismo: como señala @Neil Williams, puede extraer un AST de lambdas utilizando árboles de expresión, mientras que los métodos anónimos no se pueden utilizar de la misma manera.
ljs
Esta es una de las muchas otras ventajas de lambda. Ayuda a comprender el código mejor que los métodos anónimos. seguramente no es la intención de crear lambdas, pero estos son escenarios en los que se puede usar con más frecuencia.
Guruji
29

Esta es solo una forma de usar una expresión lambda. Puede usar una expresión lambda en cualquier lugar donde pueda usar un delegado. Esto le permite hacer cosas como esta:

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

Este código buscará en la lista una entrada que coincida con la palabra "hola". La otra forma de hacerlo es pasar un delegado al método Find, como este:

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

EDITAR :

En C # 2.0, esto podría hacerse utilizando la sintaxis de delegado anónimo:

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

Lambda ha limpiado significativamente esa sintaxis.

Scott Dorman
fuente
2
@ Jonathan Holland: Gracias por editar y agregar la sintaxis de delegado anónimo. Completa el ejemplo muy bien.
Scott Dorman
¿Qué es un delegado anónimo? // lo siento, soy nuevo en C #
HackerMan
1
@HackerMan, piense en un delegado anónimo como una función que no tiene un "nombre". Todavía está definiendo una función, que puede tener entrada y salida, pero como es un nombre, no puede referirse a ella directamente. En el código que se muestra arriba, está definiendo un método (que toma stringay devuelve a bool) como un parámetro para el Findmétodo mismo.
Scott Dorman
22

Microsoft nos ha dado una forma más limpia y conveniente de crear delegados anónimos llamados expresiones Lambda. Sin embargo, no se presta mucha atención a la parte de expresiones de esta declaración. Microsoft lanzó un espacio de nombres completo, System.Linq.Expressions , que contiene clases para crear árboles de expresión basados ​​en expresiones lambda. Los árboles de expresión están formados por objetos que representan la lógica. Por ejemplo, x = y + z es una expresión que podría ser parte de un árbol de expresión en .Net. Considere el siguiente ejemplo sencillo:

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

Este ejemplo es trivial. Y estoy seguro de que está pensando: "Esto es inútil ya que podría haber creado directamente el delegado en lugar de crear una expresión y compilarla en tiempo de ejecución". Y tú estarías bien. Pero esto proporciona la base para los árboles de expresión. Hay una serie de expresiones disponibles en los espacios de nombres de Expresiones, y usted puede construir el suyo propio. Creo que puede ver que esto podría ser útil cuando no sabe exactamente cuál debería ser el algoritmo en el momento del diseño o la compilación. Vi un ejemplo en alguna parte para usar esto para escribir una calculadora científica. También podría usarlo para sistemas bayesianos o para programación genética(AI). Algunas veces en mi carrera tuve que escribir una funcionalidad similar a Excel que permitía a los usuarios ingresar expresiones simples (suma, resta, etc.) para operar con los datos disponibles. En pre.Net 3.5, tuve que recurrir a un lenguaje de script externo a C #, o tuve que usar la funcionalidad de emisión de código en reflexión para crear código .Net sobre la marcha. Ahora usaría árboles de expresión.

Jason Jackson
fuente
12

Ahorra la necesidad de tener métodos que solo se usan una vez en un lugar específico de ser definidos lejos del lugar donde se usan. Los buenos usos son como comparadores de algoritmos genéricos como la ordenación, donde luego puede definir una función de ordenación personalizada en la que invoca la ordenación en lugar de forzarlo a buscar en otro lado para ver qué está ordenando.

Y no es realmente una innovación. LISP ha tenido funciones lambda durante aproximadamente 30 años o más.

workmad3
fuente
6

También puede encontrar el uso de expresiones lambda al escribir códigos genéricos para actuar sobre sus métodos.

Por ejemplo: función genérica para calcular el tiempo que tarda una llamada al método. (es decir, Actionaquí)

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

Y puede llamar al método anterior usando la expresión lambda de la siguiente manera,

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

Expression le permite obtener valor de retorno de su método y también param

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

La expresión lambda es una forma concisa de representar un método anónimo. Tanto los métodos anónimos como las expresiones Lambda le permiten definir la implementación del método en línea, sin embargo, un método anónimo requiere explícitamente que defina los tipos de parámetros y el tipo de retorno para un método. La expresión Lambda utiliza la función de inferencia de tipo de C # 3.0 que permite al compilador inferir el tipo de la variable en función del contexto. ¡Es muy conveniente porque eso nos ahorra mucho tipeo!

Vicepresidente de Vijesh
fuente
5

Una expresión lambda es como un método anónimo escrito en lugar de una instancia de delegado.

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

Considere la expresión lambda x => x * x;

El valor del parámetro de entrada es x (en el lado izquierdo de =>)

La lógica de la función es x * x (en el lado derecho de =>)

El código de una expresión lambda puede ser un bloque de instrucción en lugar de una expresión.

x => {return x * x;};

Ejemplo

Nota: Funces un delegado genérico predefinido.

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

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

Referencias

  1. ¿Cómo se puede utilizar indistintamente un delegado y una interfaz?
LCJ
fuente
4

Muchas veces, solo está utilizando la funcionalidad en un solo lugar, por lo que hacer un método simplemente abarrota la clase.

Darren Kopp
fuente
3

Es una forma de realizar pequeñas operaciones y ponerlas muy cerca de donde se usan (no muy diferente de declarar una variable cerca de su punto de uso). Se supone que esto hará que su código sea más legible. Al anonimizar la expresión, también está haciendo que sea mucho más difícil para alguien descifrar su código de cliente si la función se usa en otro lugar y se modifica para "mejorarla".

Del mismo modo, ¿por qué necesitas usar foreach? Puede hacer todo en foreach con un bucle simple o simplemente usando IEnumerable directamente. Respuesta: no lo necesita , pero hace que su código sea más legible.

pedestal
fuente
0

La innovación está en el tipo seguridad y transparencia. Aunque no declara tipos de expresiones lambda, se infieren y pueden usarse mediante búsqueda de código, análisis estático, herramientas de refactorización y reflexión de tiempo de ejecución.

Por ejemplo, antes de que pudieras haber usado SQL y pudieras recibir un ataque de inyección SQL, porque un hacker pasó una cadena donde normalmente se esperaba un número. Ahora usaría una expresión lambda de LINQ, que está protegida contra eso.

No es posible construir una API LINQ en delegados puros, porque requiere combinar árboles de expresión antes de evaluarlos.

En 2016, la mayoría de los lenguajes populares tienen soporte de expresión lambda , y C # fue uno de los pioneros en esta evolución entre los lenguajes imperativos convencionales.

battlmonstr
fuente
0

Esta es quizás la mejor explicación sobre por qué usar expresiones lambda -> https://youtu.be/j9nj5dTo54Q

En resumen, es para mejorar la legibilidad del código, reducir las posibilidades de errores al reutilizar en lugar de replicar el código, y aprovechar la optimización que ocurre detrás de escena.

Coffeeeee
fuente
0

El mayor beneficio de las expresiones lambda y las funciones anónimas es el hecho de que permiten que el cliente (programador) de una biblioteca / marco inyecte funcionalidad mediante código en la biblioteca / marco dada (como es LINQ, ASP.NET Core y muchos otros) de una manera que los métodos regulares no pueden. Sin embargo, su fortaleza no es obvia para un programador de aplicaciones único, sino para el que crea bibliotecas que luego serán utilizadas por otros que deseen configurar el comportamiento del código de la biblioteca o el que usa bibliotecas. Entonces, el contexto de usar efectivamente una expresión lambda es el uso / creación de una biblioteca / marco.

Además, dado que describen el código de uso único, no tienen que ser miembros de una clase en la que eso conducirá a una mayor complejidad del código. Imagine tener que declarar una clase con un enfoque poco claro cada vez que queramos configurar la operación de un objeto de clase.

Grigoris Dimitroulakos
fuente