Skip to main content

Expresiones Lambda

Una de las nuevas características introducidas en C# desde su versión 3.0 son las expresiones lambda. Estas expresiones junto a muchas otras características fueron introducidas con el fin de cubrir ciertas necesidades para la creación del lenguaje integrado de consulta (LINQ), que también se introdujo en la versión 3.

Las expresiones lambda proceden del paradigma de programación funcional y los podemos encontrar en lenguajes como LISP o Haskell. Como bien explica Octavio Hernández en su libro “C# 3.0 y LINQ” las expresiones lambda se pueden considerar como una extensión de los métodos anónimos, que ofrecen una sintaxis más concisa y funcional para expresarlos.

Para entenderlo mejor, lo primero que haremos será ver cómo hacer métodos anónimos antes de la llegada de las funciones lambda a C#. Para verlo, implementaremos la función sucesor:

Funcion_ArgEntrada_ArgSalida sucesor = delegate(int x) { return x + 1; };

      Console.WriteLine(sucesor(6));

Si la misma función sucesor la realizásemos mediante una función lambda quedaría:

Func<int, int> sucesorLambda = x => x + 1;

      Console.WriteLine(sucesorLambda(6));

Como podéis ver la expresión es mucho más clara y nos ahorramos el utilizar palabras clave como “delegate” o “return”. Además de que la expresión lambda nos permite que sea el compilador el que infiera el tipo de nuestra variable x.

También podríamos haber puesto el tipo de la variable de forma explícita:

Func<int, int> sucesorLambda = (int x) => x + 1;

Otro de los elementos que necesitamos introducir para poder comprender este último ejemplo es el tipo genérico Func, el cual soporta 5 sobrecargas, es decir, podemos utilizarlo para asignarle funciones lambda de entre 0 y 4 parámetros de entrada y con un parámetro de salida.

Viendo el ejemplo de la función sucesor, apreciamos que la sintaxis es bastante sencilla, delante del operador (=>) pondremos los parámetros separados por comas, y después de la flecha el cuerpo de nuestra función. El cuerpo a su vez puede estar formado por una sentencia o un conjunto de ellas que colocaremos entre llaves. Un ejemplo con varias sentencias es:

Func<string, int> f_num_a = (string s) => {

string cadena = s.ToLower();

             return cadena.Count((car) => car.Equals('a'));

             };

Console.WriteLine((f_num_a("abAbexaceA")).ToString());

En este último ejemplo nos alejamos de las funciones propiamente numéricas para trabajar sobre cadenas de texto, contamos el número de veces que aparece el carácter “a” dentro de la cadena pasada por parámetro.

También podemos crear una función lambda que no contenga parámetros de entrada, y en este caso, será necesario poner paréntesis vacíos:

Func<string> fun_vacia = () => "Función lambda sin parámetros";

Otro aspecto a tener en cuenta son las funciones lambda recursivas. Como con cualquier función recursiva, debemos de tener claro que realizar dicha función lambda de forma recursiva aporta eficiencia a nuestro código.

Como ejemplo de función lambda recursiva utilizaremos la función factorial de un número entero. Como peculiaridad necesitaremos primero definir la función y luego asignarle el comportamiento mediante una función lambda.

Func<int, int> factorial = null;

      factorial = x => x == 0 ? 1 : x * factorial(x - 1);

      Console.WriteLine((factorial(4)).ToString());

Funciones Lamda y LINQ

Uno de los valores más importantes de las funciones lambda es su uso en consultas LINQ, especialmente su uso en el operador Where. Esto es gracias a que el parámetros que se utiliza al llamar al método Where de la clase Enumerable es un tipo delegado System.Func(T, TResult). Para que comprendáis mejor lo que veremos a continuación, nos basaremos en la siguiente sonsulta LINQ:

varaprobados = from alumno in Alumno.Alumnos()

                    where alumno.Nota >= 5

                      orderby alumno.Nota descending

                      select alumno;

Cuando utilizamos el operador Where sobre un IEnumerable<Objeto>, el compilador inferirá que el parámetro de entrada es del tipo de dicho Objeto, permitiéndonos acceder a sus métodos y propiedades sin necesidad de especificar el tipo explícitamente.

Como ejemplo consultaremos sobre una lista de alumnos de una asignatura para obtener los alumnos que la han aprobado. Lo primero será crear una clase Alumno con un método estático que nos devuelva una lista de alumnos.

public class Alumno

   {

       public string Nombre {get; set;}

       public string Apellidos {get; set;}

       public int Nota { get; set; }

       public Alumno(string nombre, string apellidos, int nota)

       {

           Nombre = nombre;

           Apellidos = apellidos;

           Nota = nota;

       }

       public static List<Alumno> Alumnos()

       {

           return new List<Alumno>(){

new Alumno("Alex", "Murillo", 9),

new Alumno("Marta", "Orio", 10),

new Alumno("Mario", "Sánchez", 3),

new Alumno("Almudena", "López", 8),

new Alumno("Miguel", "De la Vega", 4) };

       }

   }

Y realizamos un par de funciones lambda, una para obtener los alumnos que como nota estén aprobados y otra que nos permitirá ordenarlos descendentemente por nota.

varaprobados = Alumno.Alumnos()

.Where(alumno => alumno.Nota >= 5)

.OrderByDescending(alumno => alumno.Nota);

      foreach(Alumno a in aprobados)

      {

           Console.WriteLine(a.Nombre);

      }

Funciones Lambda como representación de expresiones

En este apartado, nos referimos a utilizar las funciones lambda como árboles de expresiones. Un árbol de expresiones es una secuencia de objetos o acciones almacenadas en memoria para su futura evaluación.

Con esta técnica el compilador de .NET no almacena las funciones como código ejecutable, sino que crea el código de la expresión para llenar el árbol. ¿Pero cómo distingue el compilador si queremos una función lambda como árbol de expresiones o como una función anónima?

A diferencia de la sintaxis que utilizábamos para las funciones lambda como funciones anónimas, no vamos a asignarlas a delegados (por ejemplo, Func), sino que usaremos el tipo Expression. Por ejemplo, vamos a hacer que el compilador almacene la expresión que utilizamos para resolver las funciones sucesor, sin que lo traduzca a código ejecutable.

ParameterExpressionvar_x = Expression.Parameter(typeof(Int32), "x");

           Expression<Func<Int32, Int32>> expresion_sucesor =

               Expression.Lambda<Func<Int32, Int32>>(

                   Expression.Add(var_x, Expression.Constant(Int32.Parse("1")))

               );

Func<int, int> expresion_sucesor_compilada = expresion_sucesor.Compile();

Console.WriteLine(expresion_sucesor_compilada(6));

Si definimos la función lambda como expresión podemos utilizar tipado fuerte y ayudarnos del intellisense. Además de cometer menos errores al codificar la función lambda, esta estructura nos permitirá en tiempo de ejecución, recorrerla, serializarla, analizarla y transformarla según nos interese.

En un principio esto pueda parecer de poca utilidad, pero es una pieza clave en LINQ. En LINQ, el almacenar la expresión es lo que nos permite utilizar diferentes proveedores. Por ejemplo, en LINQ to SQL utilizaremos las expresiones lambdas almacenadas con el objetivo de manipular expresiones y posteriormente convertirlas en sentencias SQL que serán enviadas al motor de SQL.

También podemos utilizarlo en ASP.NET MVC para crear enlaces hacia las acciones dentro de las vistas. Como ejemplo veremos cómo acceder a la acción “Ver perfil” dentro del controlador “MiCuenta”:

Html.ActionLink<MiCuentaController>
              (accion =>accion.VerPerfil(), "Ver Perfil");

Si alguno os gustaría aprender algo más sobre para qué utilizar las funciones lambda como árboles de expresión, os invito a que lo investiguéis desde el punto de vista de cálculo simbólico. Visitad este blog en el que explica cómo crear un Framework matemático utilizando dicha técnica.

 

Como conclusión:

Muchas son las características que se incluyeron para .NET Framework 3.5, pero sin lugar a duda una de las novedades que más huella ha dejado es LINQ. Esta novedad trajo consigo una serie de avances que después de unos meses usándolas se han convertido en parte fundamental de nuestro código. ¡No sé cómo podíamos hacer lo mismo sin ellas!

Las funciones lambdas son una de estas novedades, que nos han permitido no sólo disminuir el número de líneas para hacer una función y por lo tanto aumentar nuestra productividad, sino que nos permiten tener un código más claro y eficaz.

Microsoft está realizando una encuesta en línea para comprender su opinión del sitio web de. Si decide participar, se le presentará la encuesta en línea cuando abandone el sitio web de.

¿Desea participar?