2010-01-30 16 views
35

En mi búsqueda para entender el aspecto raro '=>' operador, he encontrado una buena place to start, y el autor es muy concisa y clara:Lambda para Dummies .... ¿Alguien, alguien? Creo que no

parameters => expression 

¿Alguien tiene ¿Algún consejo sobre la comprensión de los conceptos básicos de lambdas para que sea más fácil 'descifrar' las declaraciones lambda más complejas?

Por ejemplo: si me dan algo así como (a partir de una answer I received here):

filenames.SelectMany(f => 
     Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true) 
     .Cast<PluginClassAttribute>() 
     .Select(a => a.PluginType) 
).ToList(); 

¿Cómo puedo ir sobre romper esto abajo en piezas más simples?


ACTUALIZACIÓN: quería mostrar mi primera expresión lambda. No te rías de mí, pero lo hice sin copiar el ejemplo de alguien ... y funcionó la primera vez:

public ModuleData[] GetStartModules() 
{ return modules.FindAll(start => start.IsBatch == true).ToArray(); } 
+0

Básicamente, lambdas son como cualquier otra función, excepto que se definen donde los necesite. ¿Estás familiarizado con las clases anónimas? son así solo por métodos. (publicado como un comentario porque no podía esperar acercarme a la calidad de las otras respuestas) – RCIX

+0

Voy a insertar descaradamente una referencia de Jon Skeet: http://csharpindepth.com/Articles/Chapter5/Closures.aspx Esto realmente tiene ayudó a aumentar mi comprensión de las expresiones lambda. Muy buena lectura, por supuesto! – IAbstract

+3

Te recomiendo que visites Lambdaexpression.net – Delashmate

Respuesta

39

Analicemos el ejemplo de código:

filenames.SelectMany(f => 
     Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true) 
     .Cast<PluginClassAttribute>() 
     .Select(a => a.PluginType) 
).ToList(); 

Por lo tanto, partimos de una llamada string[]filenames. Invocamos el método SelectMany extensión en la matriz, y luego invocamos ToList en el resultado:

filenames.SelectMany(
    ... 
).ToList(); 

SelectMany toma un delegado como parámetro, en este caso el delegado debe tener un parámetro del tipo string como entrada, y devuelva un IEnumerable<T> (donde se infiere el tipo de T). Aquí es donde entran en la etapa lambdas:

filenames.SelectMany(f => 
     Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true) 
).ToList() 

¿Qué ocurrirá aquí es que para cada elemento de la matrizfilenames, el delegado se invocará. f es el parámetro de entrada, y lo que viene a la derecha de => es el cuerpo del método al que hace referencia el delegado. En este caso, Assembly.LoadFrom se invocará para el nombre de archivo en la matriz, pasando el nombre de archivo en el método LoadFrom utilizando el argumento f. En el AssemblyInstance que se devuelve, se invocará GetCustomAttributes(typeof(PluginClassAttribute), true), que devuelve una matriz de instancias Attribute. Por lo tanto, el compilador no puede inferir que el tipo de T mencionado anteriormente es Assembly.

En el IEnumerable<Attribute> que se devuelve, se invocará Cast<PluginClassAttribute>(), devolviendo un IEnumerable<PluginClassAttribute>.

Así que ahora tenemos un IEnumerable<PluginClassAttribute>, e invocamos Select en él.El método Select es similar a SelectMany, pero devuelve una única instancia del tipo T (que se deduce del compilador) en lugar de IEnumerable<T>. La configuración es idéntica; para cada elemento en el IEnumerable<PluginClassAttribute> que invocará el delegado definido, pasando el valor del elemento corriente en él:

.Select(a => a.PluginType) 

Una vez más, a es el parámetro de entrada, a.PluginType es el cuerpo del método. Por lo tanto, para cada instancia PluginClassAttribute en la lista, devolverá el valor de la propiedad PluginType (supongo que esta propiedad es del tipo Type).

Resumen Ejecutivo
Si pegamos esos trozos y piezas juntas:

// process all strings in the filenames array 
filenames.SelectMany(f => 
     // get all Attributes of the type PluginClassAttribute from the assembly 
     // with the given file name 
     Assembly.LoadFrom(f).GetCustomAttributes(typeof(PluginClassAttribute), true) 
     // cast the returned instances to PluginClassAttribute 
     .Cast<PluginClassAttribute>() 
     // return the PluginType property from each PluginClassAttribute instance 
     .Select(a => a.PluginType) 
).ToList(); 

Lambdas vs delegados
Vamos a terminar esto mediante la comparación de lambdas a los delegados. Tome la siguiente lista:

List<string> strings = new List<string> { "one", "two", "three" }; 

decir que queremos filtrar los que comienza con la letra "t":

var result = strings.Where(s => s.StartsWith("t")); 

Este es el método más común; configúralo usando una expresión lambda. Pero hay alternativas:

Func<string,bool> func = delegate(string s) { return s.StartsWith("t");}; 
result = strings.Where(func); 

Esto es esencialmente lo mismo: primero creamos un delegado del tipo Func<string, bool> (lo que significa que se necesita un string como parámetro de entrada y devuelve un bool). Luego pasamos ese delegado como parámetro al método Where. Esto es lo que el compilador hizo para nosotros detrás de escena en la primera muestra (strings.Where(s => s.StartsWith("t"));).

Una tercera opción es simplemente pasar un delegado a un método no anónimo:

private bool StringsStartingWithT(string s) 
{ 
    return s.StartsWith("t"); 
} 

// somewhere else in the code: 
result = strings.Where(StringsStartingWithT); 

Así, en el caso que estamos viendo aquí, la expresión lambda es una forma más compacta de la definición de una delegar, generalmente refiriendo un método anónimo.

Y si tuviera la energía leído hasta aquí, bueno, gracias por su tiempo :)

+0

@Fredrik Mörk: Excelente explicación y gracias por la ayuda en "diagramación" de la declaración. Es intimidante cuando no entiendes la versión abreviada. Entre usted y Kastermester, creo que tengo esto ... ¡y no es un tren! ¡Gracias de nuevo! – IAbstract

+1

Esta es una gran respuesta y responde a una de las preguntas reales que el resto de nosotros parecía ignorar: ¡excelente trabajo! – kastermester

+1

Gran respuesta +1 – xandercoded

0

Lambda calculus es común en muchos lenguajes de programación. También se llaman funciones anónimas en algunos idiomas. Aunque los diferentes idiomas tienen una sintaxis diferente para lambda, el principio es el mismo, y sus diversas partes suelen ser idénticas.

Quizás el más famoso es el de las funciones anónimas de Javascript.

lol = function() {laugh()} 
# is equivalent to 
function lol() {laugh()} 

¿Cuál es la diferencia? Bueno, a veces no quieres pasar por el problema de crear una función solo para pasarla a alguna parte una vez y nunca más.

window.onload = function() {laugh()} 
# is way easier than 
function lol() {laugh()} 
window.onload = lol 

Se puede ver the wikipedia article para obtener información indept o puede saltar directamente a Lambda in programming en el mismo artículo.

5

Bueno, puedes ver una lambda como una manera rápida de escribir un método que solo deseas usar una vez. Por ejemplo, el método

private int sum5(int n) 
{ 
    return n + 5; 
} 

siguiente es equivalente a la lambda: (n) => n + 5. Excepto que puede llamar al método en cualquier lugar de su clase, y la lambda solo vive en el alcance que está declarado (También puede almacenar lambdas en objetos Action y Func)

Otra cosa que lambdas puede hacer por usted es capturar alcance , construyendo un cierre. Por ejemplo, si tiene algo como esto:

...methodbody 
int acc = 5; 
Func<int> addAcc = (n) => n + acc; 

Lo que tienes no es una función que acepta un argumento que antes, pero la cantidad añadida se toma del valor de la variable. La lambda puede vivir incluso después de que el alcance en el que se definió acc haya finalizado.

Puedes construir cosas muy aseadas con lambdas, pero debes tener cuidado, porque a veces pierdes legibilidad con trucos como este.

6

Por lo tanto, para comenzar con la definición aterradora, una lambda es otra forma de definir un método anónimo. Ha habido (desde C# 2.0 creo) una forma de construir métodos anónimos, sin embargo esa sintaxis era muy ... inconviniente.

¿Qué es un método anónimo? Es una forma de definir un método en línea, sin nombre, por lo tanto, es anónimo. Esto es útil si tiene un método que toma un delegado, ya que puede pasar esta expresión lambda/método anónimo como parámetro, dado que los tipos coinciden. Tome IEnumerable.Select como ejemplo, se define de la siguiente manera:

IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector); 

Si se va a utilizar este método normalmente en decir una lista y seleccionar cada elemento dos veces (que se concatena a sí mismo):

string MyConcat(string str){ 
    return str + str; 
} 

... 

void myMethod(){ 
    IEnumerable<string> result = someIEnumerable.Select(MyConcat); 
} 

Esta es una manera muy inconviniente de hacerlo, especialmente si desea realizar muchas de estas operaciones; también suele ser este tipo de métodos que solo utiliza una vez. La definición que mostró (parameters => expression) es muy concisa y la ubica en el lugar correcto. Lo interesante de lambda es que no necesita expresar el tipo de los parámetros: , siempre que se puedan derivar de la expresión. Es decir. en el caso de Select - nosotros sabemos el primer parámetro debe ser de tipo TSource - porque la definición del método lo indica. Además, si llamamos al método solo como foo. Seleccione (...) entonces el valor de retorno de la expresión definirá TResult.

Una cosa interesante también es que para una declaración la palabra clave de retorno lambda no es necesaria: la lambda devolverá lo que evalúa esa expresión. Sin embargo, si usa un bloque (incluido en '{' y '}'), debe incluir la palabra clave return como siempre.

Si se desea, todavía es 100% legal definir los tipos de los parámetros. Con este nuevo conocimiento, vamos a tratar de reescribir el ejemplo anterior:

void myMethod(){ 
    IEnumerable<string> result = someIEnumerable.Select(s => s + s); 
} 

O, con parámetros explícitos declaró

void myMethod(){ 
    IEnumerable<string> result = someIEnumerable.Select((string s) => s + s); 
} 

Otra característica interesante de la lambda en C# es su uso para construir expression trees. Sin embargo, probablemente este no sea un material "principiante", pero en resumen, un árbol de expresiones contiene todos los metadatos sobre un lambda, en lugar de un código ejecutable.

+0

@kastermester: gracias por las explicaciones. Creo que una cosa que me echó fue el 'tipo' inferido de los parámetros. ¡¡gran trabajo!! – IAbstract

+0

. De nada, me alegro de poder ayudarlo. Puede ser bastante difícil entender esto las primeras veces que los encuentras en el código C#; una vez que lo obtienes, pueden hacer mucho más fácil, y son muy útiles, no solo en conjunción con LINQ. – kastermester

0

Esto es solo la notación de C# para anotar el valor de una función. No es necesario dar un nombre a la función, por lo tanto, este valor se denomina a veces función anónima. Otros idiomas tienen otras anotaciones, pero siempre contienen una lista de parámetros y un cuerpo.

La notación original, inventado por Alonzo Church por su Lambda calculus en los 1930ies utilizó el lambda carácter griego en la expresión λx.t para representar una función, de ahí el nombre.

2

Como han dicho otros, una expresión lambda es una notación de una función. Es vincula las variables libres en el lado derecho de la expresión a los parámetros de la izquierda.

a => a + 1 

crea una función que se une la variable libre una mesa en la expresión (a + 1) para el primer parámetro de una función y devuelve esa función.

Un caso en el que las lambdas son extremadamente útiles es cuando las usa para trabajar con estructuras de listas. The System.Linq.La clase Enumerable proporciona muchas funciones útiles que le permiten trabajar con expresiones Lambda y objetos que implementan IEnumerable. Por ejemplo Enumerable.Where se puede utilizar para filtrar una lista:

List<string> fruits = new List<string> { 
     "apple", "passionfruit", "banana", "mango", 
     "orange", "blueberry", "grape", "strawberry" }; 

IEnumerable<string> shortFruits = fruits.Where(fruit => fruit.Length < 6); 

foreach (string fruit in shortFruits) { 
    Console.WriteLine(fruit); 
} 

La salida será "manzana, mango, uva".

tratar de entender lo que está pasando aquí: la expresión fruta => fruit.Length < 6 crea una función que volver verdadero si la propiedad Longitud del parámetro es menor que 6.

bucles sobre el Enumerable.Where Lista y crea una nueva lista que contiene solo aquellos elementos para los cuales la función suministrada devuelve verdadero. Esto le ahorra escribir código que itera sobre la Lista, verifica un predicado para cada elemento y hace algo.

0

Mi consejo para comprender los conceptos básicos de lambdas es doble.

En primer lugar, recomiendo aprender sobre programación funcional. Haskell es un buen lenguaje para empezar en ese sentido. El libro que estoy usando y obteniendo mucho, es Programming in Haskell por Graham Hutton. Esto proporciona una buena base en Haskell e incluye explicaciones de lambdas.

A partir de ahí, creo que debería ver Erik Meijer's lectures on Functional Programming, ya que también proporcionan una gran introducción a la programación funcional, también usan Haskell y cruzan a C#.

Una vez que hayas asimilado todo lo que deberías estar en camino a comprender lambdas.

2

Una buena explicación sencilla dirigida a los desarrolladores que están firmiliar con la codificación, pero no con lambdas es este sencillo video en TekPub

TekPub - Concepts: #2 Lambdas

obviamente tienes un montón de comentarios aquí, pero esto es otra buena fuente y una explicación sencilla.

+0

+1 buen video clip ... mi sonido está roto :(Tengo la esencia de eso ... thx – IAbstract

1

Sé que esto es un poco viejo pero vine aquí tratando de darle sentido a todo esto lambda. En el momento en que terminé de verter más de todas las respuestas y comentarios, tuve una mejor understnading de lambda y pensé que simplemente debería agregar esta respuesta simple (desde la perspectiva del alumno para los estudiantes):

Dont confundir a => a + 1 en el sentido de añadir al menos 1 a a y devuelve el resultado a a. (Esto probablemente sea una fuente de confusión para los principiantes. En vez de verlo así: a es el parámetro de entrada en una función (función sin nombre) y a + 1 es la declaración (es) en la función (función sin nombre construida 'en al vuelo ").

Espero que esto ayude :)

Cuestiones relacionadas