Nueva característica de “Orcas”: Expresiones Lambda

El mes pasado empezé una serie de post sobre las nuevas características de C# y VB que vienen con las release de Visual Studio y el .NET Framework "Orcas". Aquí están los dos primeros post:

 El post de hoy cubre otra caracterísitca fundamental: Expresiones Lambda

¿Qué son las Expresiones Lambda?

C#2.0 ( que venía con VS 2005) introdujo el concepto de métodos anónimos, que permitía escribir bloques de código donde se esperaban delegados.

Las Expresiones Lambda aportan una sintaxis más concisa y funcional para escribir métodos anónimos. Son extremadamente útiles cuando escribimos peticiones LINQ - permiten una forma muy compacta de escribir funciones que puedan ser pasadas como argumentos para una evaluación posterior.

Ejemplo de Expresiones Lambda:

En el post sobre Métodos de extensión, demostramos cómo podemos declarar una clase simple, "Person" como sigue:

Os mostramos cómo podemos instanciar una colleción List<Person> con valores, y usar los nuevos métodos de extensión "Where" y "Average" de LINQ para obtener un subconjunto de personas de la colección, así como calcular la media de edad de la gente de dicha colección:

Las expresiones subrayadas en rojo, son Expressiones Lambda. En el ejemplo de arriba usamos la primera expresión lambda para especificar el filtro a usar para obtener a las personas, y la segunda expresión lambda la usamos para especificar el valor de los objetos Person para calcular la media.

Explicaciones de las expresiones Lambda.

La forma más sencilla para conceptualizar las expresiones lambda es pensar en ellas como formas de escribir métodos breves en una linea. Por ejemplo, el ejemplo anterior, podría haberlo escrito usando métodos anónimos de C# 2.0 de la siguiente forma:

Los dos métodos anónimos de arriba reciben un objeto de tipo Person como parámetro. El primer método anónimo devuelve un booleano (indicando si su apellido es Guthrie). El segundo método anónimo devuelve un entero (int) (que devuelve la edad de la persona). La expresión lambda que usamos más arriba funciona de la misma forma - las dos expresiones reciben un tipo Person como parámetro. La primera lambda devuelve un booleano, y la segunda lambda devuelve un entero.

En C# una expresión lambda es escrita, sintácticamente, como una lista de parámteros, seguido del token "=>", y seguido por una expresión o un bloque de sentencias para que se ejecuten cuando se invoque la expresión

params =>expression

Así, cuando escribimos la expresión lambda:

p=>p.LastName=="Guthrie"

estamos indicando que la expresión lambda que estamos definiendo recibe un parámetro "p", y que la expresión del código que se va a ejecutar devuelve el valor p.LastName que sea igual a "Guthrie". El echo de que llamemos "p" al parámetro es irrelevante - podria haberlo llamado "o", "x", "foo" o cualquier nombre que hubiese querido.

Al contrario que con los métodos anónimos, que requieren declaraciones de los tipos de los parametros explícitamente, las expresiones Lambda permiten omitir los tipos de los parámetros dejando que se infieran basándose en su uso. Por ejemplo, cuando escribo la expresión lambda p=>p.LastName=="Guthrie", el compilador infiere que el tipo del parámetro p será del tipo Person porque el método de extensión "Where" estaba trabajando con una collección List<Person>.

Los tipos de parametros Lambda pueden ser inferidos tanto en tiempo de compilación y por el motor de intellisense de Visual Studio (esto significa que tenemos soporte completo de intellisense cuando estamos escribiendo expresiones lambda). Por ejemplo, cuando escribimos "p" vemos cómo Visual Studio "Orcas" nos muestra en el intelisense que sabe que "p" es del tipo "Person".

Nota: Si queréis declarar explícitamente el tipo del parametro de una expresión Lambda, podeis hacerlo declarando el tipo del parámetro ántes del nombre del parámetro de la siguiente forma:

Avanzado: Árboles de expresiones lambda para desarrolladores de frameworks.

Una de las cosas que hace que las expresiones lambda sean muy útiles desde el punto de vista de un desarrollador de frameworks es que pueden ser compiladas como cualquier otro delegado (en forma de un método basado en IL) o como un objeto árbol de expresiones que pueden ser usados en runtime para analizar, transformar u optimizar la expresión.

De expresiones lambda a código delegado.

El método de extensión "Where" de arriba es un ejemplo de compilar una expresión lambda a un código delegado (significando la compilación a IL que es llamado en forma de delegado). El método de extensión "Where" para filtrar cualquier colección IEnumerable como el de arriba puede ser implementado usando el método de extensión de abajo:

Al método de extensión Where() de arriba se le pasa un parámetro de filtrado del tipo Func<T,bool>, que es un delegado que con un sólo parámtero del tipo "T" devuelve un booleano que indica cuándo se cumple la condición. Cuando pasamos una expresión Lambda como parámetro al método de extensión Where(), el compilador de C# compilará la expresión lambda a un delegado en IL (donde el tipo <T> será Person) para que nuestro método Where() pueda llamarlo para evaluar si se cumple la condición.

De expresiones Lambda a arbol de expresiones.

Compilando expresiones lambda a código delegado funciona muy bien cuando queremos evaluarlas con objetos en memoria como nuestra colección List de arriba. Pero consideremos casos donde queramos consultar datos de una base de datos (el código siguiente fué escrito usando el mapper de "Orcas" LINQ to SQL)

Aquí estamos obteniendo una secuencia fuertemente tipada de objetos "Product" de la base de datos, y estamos expresando un filtro usando expresiones lambda para el método de extensión "Where()".

Lo que no queremos es obtener todas las filas de productos de la base de datos,  introducirlos en una colección en local y ejecutar el método de extension Where() para objetos de memoria. Esto sería muy ineficiente y no sería escalable con grandes bases de datos. En lugar de eso, nos gustaría usar el ORM LINQ to SQL para traducir el filtro lambda en una expresión SQL, y ejecutar el filtro en la base de datos. De esa forma sólo se devolverán aquellas filas que cumplan el filtro ( y será mucho más eficiente).

Los desarrolladores de frameworks pueden conseguir esto declarando los parámetros de sus expresiones lambda del tipo Expression<T> en lugar de Func<T>. Esto hará que la expressión lambda sea compilada como un arbol de expresiones que podamos ponerla aparte y analizarla en tiempo de ejecución:

Fijaos cómo cogemos la misma expresión lambda p=>p.LastName == "Guthrie" que usamos ántes, pero esta vez asignada a una variable Expression<Func<Person,bool>> en lugar de a un tipo Func<Person,bool>. Entonces, cuando se genere el IL, el compilador asignará un arbol de expresiones que podremos usar como desarrolladores de framework para analizar la expresión lambda y evaluarla cuando queramos (por ejemplo, podríamos elegir los tipos, nombres y valores declarados en la expresión).

En el caso de LINQ to SQL, podría cojer este filtro lambda y traducirlo en una SQL estándar para ejecutarla contra una base de datos (logicamente "SELECT * FROM Products WHERE UnitPrice<55").

Interfaz IQueryable<T>

Para ayudar a los desarrolladores de frameworks en la construcción de providers de datos, LINQ aporta la interfaz IQueryable<T>. Implementa los métodos de extensión, operadores estándar de LINQ, y aporta una forma más eficiente de implementar el procesado de un complejo árbol de expresiones (por ejemplo: algo parecido al siguiente caso, donde usamos tres métodos de extensión diferentes y dos lambdas para obtener 10 productos de la base de datos:

Para leer algunos post sobre cómo construir un proveedor para LINQ con usando IQueryable<T>, pasaros por estos post:

Resúmen.

Afortunadamente el post de arriba nos dá un entendimiento básico de cómo hay que pensar y usar las expresiones lambda. Cuando se combina con los métodos de extensión de LINQ, nos ofrece una forma muy rica de consultar e interactuar con cualquier tipo de datos conservando el soporte en tiempo de compilación e intellisense.

Con el soporte de Expresiones en arbol que nos dan las lambdas y la interfaz IQueryable<T>, los desarrolladores de frameworks pueden construir proveedores de datos que se ejecutarán rápida y eficientemente sobre las fuentes de datos (tanto bases de datos, archivos xml, objetos en memoria, servicios web, sistemas LDAP, etc).

En las próximas semanas completaré esta serie de post cubriendo los nuevos conceptos de lenguajes desde un nivel teórico, y luego hablaré sobre algunos ejemplos muy prácticos usando esos conceptos (especialmente usando LINQ contra bases de datos y archivos XML).

Espero que sirva.

Scott.

Traducido por: Juan María Laó Ramos. Microsoft Student Partner.

7 pensamientos en “Nueva característica de “Orcas”: Expresiones Lambda

  1. Pingback: Nueva característica de “Orcas”: Sintaxis de consultas « Thinking in .NET

  2. Pingback: LINQ y sintaxis lambda | Climens Codelog

  3. Pingback: LINQ to XML (como crear rss reader) | Un Mundo Interesante ....

  4. noel

    How I can take this expression using ac # 2.0 anonymous methods and lambda expressions do not
    private static string JoinAddresses(IList mailboxes)
    {
    return string.Join(“,”,new List(mailboxes).ConvertAll(m => string.Format(“{0} “, m.Name, m.Address)).ToArray());
    }

    Responder
  5. noel

    cómo puedo llevar este método con c# 2.0 a metodos anónimos sin utilizar las expresiones lambda, al menos necesito saber que se hace aquí.
    private static string JoinAddresses(IList mailboxes)
    {
    return string.Join(“,”,new List(mailboxes).ConvertAll(m => string.Format(“{0} “, m.Name, m.Address)).ToArray());
    }

    Responder

Deja un comentario