viernes, 20 de febrero de 2015

De los delegados a las lambdas 1: Comenzando

Inicio una nueva serie de post (3 en principio) dedicados a los delegados y las lambdas... que hace tiempo se convirtieron en algo imprescindible para todo desarrollador C#.

Empecemos por el principio: que es un delegado, métodos anónimos, predicados y todas esas cosas que algunos desarrolladores usan, sin llegar a comprender lo que hacen.

¿Qué es un delegado? 

 
Algo así (un poco de código vale mas que mil palabras):

   1:  delegate <return type> <delegate-name> <parameter list>

Un ejemplo:
 
   1:  delegate int delOperacionSobreEnteros(int num1, int num2);

Si eres de la vieja escuela habrás programado en C / C++ (que tiempos) así que lo más fácil es decir que es muy similar a un puntero a función.

Pero si no, te diré que un delegado es un tipo que permite almacenar un método. En el ejemplo anterior el delegado delOperacionSobreEnteros permite representar métodos que cumplan con su firma: recibe dos enteros como parámetros y devuelve un entero. Puesto que un delegado almacena un método, puede ser ejecutado.

Uno de los principales usos es definir delegados en los parámetros de un método, lo que nos permite mucha flexibilidad:

   1:  class Program
   2:  {
   3:      public delegate int delOperacionSobreEnteros(int num1, int num2);
   4:      static void Main(string[] args)
   5:      {
   6:   
   7:          delOperacionSobreEnteros d1 = OperacionSuma;
   8:          delOperacionSobreEnteros d2 = new delOperacionSobreEnteros(OperacionMultiplicacion);
   9:   
  10:          EjecutaOperacion(d1);
  11:          EjecutaOperacion(d2);            
  12:              
  13:          Console.ReadLine();
  14:      }
  15:   
  16:      protected static void EjecutaOperacion(delOperacionSobreEnteros delegado)
  17:      {
  18:          const int a=3;
  19:          const int b=2;
  20:              
  21:          Console.WriteLine("Vamos a llamar al delegado");
  22:          var res=delegado(a, b);
  23:          Console.WriteLine("a op b={0}", res);
  24:      }
  25:   
  26:      protected static int OperacionSuma(int a, int b)
  27:      {
  28:          return a + b;
  29:      }
  30:   
  31:      protected static int OperacionMultiplicacion(int a, int b)
  32:      {
  33:          return a * b;
  34:      }
  35:  }
 
Como veis tengo un método que se encarga de ejecutar operaciones (EjecutaOperacion). ¿Qué operación? La recibida como parámetro a través de un delegado. La salida del anterior ejemplo es la siguiente:
 

 
También podemos hacer que una propiedad de una clase sea un delegado...  seguro que se os ocurren más usos :-P
 

¿Y que es eso de los métodos anónimos?


Pues más de lo mismo, pero con sintaxis "reducida". Nos permiten asignar un bloque de código a un delegado, sin tener que declarar un método. Son por tanto métodos sin nombre, solo con cuerpo.

   1:  public delegate int delOperacionSobreEnteros(int num1, int num2);
   2:   
   3:  ...
   4:   
   5:  delOperacionSobreEnteros d3 = delegate (int a, int b)  { return a / b ;  };
   6:  var resd3=d3(10,2);
   7:  Console.WriteLine("10 / 2={0}", resd3);
 
Estos métodos anónimos han sido reemplazados por las lambdas, mucho más potentes y cómodos. Lo veremos en las siguientes entradas


Otros tipos de delegados

 
Si... hay más. ¿ habéis usado TaskFactory para ejecutar algo de un modo asíncrono ?
Pues bien, aunque tiene varias sobrecargas, vamos a fijarnos en estas:
 
   1:  public Task StartNew( Action action)
   2:  public Task StartNew<TResult>(Func<TResult>)  

La primera permite ejecutar código asíncrono que no devuelve nada. Veis que recibe un parámetro tipo Action
La segunda permite ejecutar código asíncrono que devolverá un valor, y recibe un parámetro tipo Func
 
¿Y que son action y Func?  Pues otros tipos de delegados.
 
Action <T1, T2,...> es un delegado que puede almacenar un método que recibe hasta 16 parámetros, pero que no retorna valor (void)
Func <T1,T2..., TResult>  también almacena un método con hasta 16 parámetros, pero puede devolver un valor
 
Veámos un ejemplo con Func:
 
   1:  Func<int, int, int> funcSuma = OperacionSuma;
   2:  var resFunc = funcSuma.Invoke(6, 7);
   3:  Console.WriteLine("6 + 7={0}", resFunc);
 
Como veis, me ahorro la definición del delegado y utilizo uno que ya viene de serie con el framework. ¿Más cómodo no?

¿Y predicate?

De nuevo es un delegado que viene definido en .NET Framework (msdn). En este caso se trata de un delegado que recibe un elemento como parámetros y que devuelve un booleano:

   1:  public delegate bool Predicate<in T>(
   2:      T obj
   3:  )

Se usa principalmente en ciertos métodos de filtrado sobre los tipos de datos Array y List. Mirad el siguiente ejemplo, creo que es autoexplicativo:  
 
 
   1:  Predicate<int> predMayor = MayorQue10;
   2:   
   3:  var lista = new List<int>() { 3, 6, 15, 45 };
   4:   
   5:  var listaFiltrada = lista.FindAll(predMayor);
   6:   
   7:  Console.WriteLine("Elementos de la lista mayores que 10:");
   8:  foreach(var elem in listaFiltrada)
   9:      Console.Write("{0} ", elem);
  10:   
  11:   
  12:  protected static bool MayorQue10 (int valor)
  13:  {
  14:      return (valor > 10);
  15:  }
 
 
Con esto terminamos, espero que lo anterior os resulte claro y útil.
 
Como siempre aquí os dejo el código.

No hay comentarios:

Publicar un comentario