jueves, 2 de agosto de 2007

Introducción a Delegados, métodos anónimos, Predicados, etc.

Introducción

Los delegados son una característica muy interesante del framework que nos permiten pasar métodos como parámetros de otros métodos, extraño, puede ser, pero es menos complicado de lo que parece. En el framework 2 nos podemos encontrar con los delegados por todas partes, en diferentes formas, como delegados, como predicados, como acciones.

¿Qué es un delegado?

Como dije antes un delegado nos permite pasar un método como parámetro de otro método, ok, sabemos cómo hacer para pasar tipos de datos como parámetros, simplemente declaramos en tipo y el nombre, detengámonos un momento, si yo quisiera indicar que en cierto lugar espero un método para utilizarlo qué cosas tengo que establecer. La respuesta es clara, el tipo de retorno del método y los parámetros que acepta, bueno, esto es justamente un delegado, una firma que nos permite establecer cómo deberá ser el método que esperamos.

Ejemplo 1

Vamos a ver la declaración de un delegado para utlizarlo en un método nuestro como parámetro.

   1:  public delegate void MiDelegado(string mensaje);

Es muy claro que si quitamos la palabra delegate de esta declaración es idéntica a una declaración de un método cualquiera. Ahora vamos a crear un método que espere un delegado del tipo MiDelegado para invocar el método dentro.

   1:          public void TestDelegado(MiDelegado delegado)
   2:          {
   3:              delegado("hola delegado loco");
   4:          }


Ok, este método va a tomar como parámetro un delegado del tipo MiDelegado (o sea un método que devuelva void y tome un string como parámetro) y luego lo va a invocar. Veamos la llamada al método.
Primero creamos el método que utlizaremos como parámetro del tipo MiDelegado.


   1:  public void MetodoDelegado(string mensaje)
   2:  {
   3:          Console.WriteLine(mensaje);
   4:  }

Sencillo, el método recibe un string lo escribe en la consola y no devuelve nada. Ahora vamos a ver cómo se invoca.

   1:  private void Test()
   2:  {
   3:          MiDelegado delegado = new MiDelegado(MetodoDelegado);
   4:          TestDelegado(delegado);
   5:  }


Más sencillo aún, lo corremos y vemos que funciona.




Vemos que en la línea 3 declaramos el delegado y le pasamos como parámetro el método que cumple con el delegado, esto lo podríamos haber resumido así


   1:  TestDelegado(new MiDelegado(delegate(string message)
   2:  {
   3:      Console.WriteLine(message);
   4:  }));

Lo que hacemos aquí es definir en la misma línea el delegado y la implementación, esto es lo que se conoce como método anónimo. Es un método anónimo justamente porque no tiene nombre, si bien a simple vista puede parecer que el código se pone confuso, en muchas ocasiones es más práctico utilizar un método anónimo.

¿Y qué es un predicado?

Si utilizaron listas genéricas se habrán encontrado con que algunos método, como Find por ejemplo, toman como parámetro un predicado, bien, esto no es más que un delegado como el que acabamos de crear pero con una firma diferente, vamos a ver la definición,

 
   1:  public delegate bool Predicate<T>(T obj);


Está claro que es un delegado que define un método que acepte un tipo T (cualquiera) y que retorne bool. Esto es para indicar que el objeto que pasa como parámetro cumple con el criterio de búsqueda, vamos a ver el método Find de la lista genérica. Echamos mano del Reflector para variar y vemos esto:

   1:  public T Find(Predicate match) 
   2:  {
   3:      if (match == null)
   4:      {
   5:          ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
   6:      }
   7:      for (int i = 0; i < this._size; i++)
   8:      {
   9:          if (match(this._items[i]))
  10:          {
  11:              return this._items[i];
  12:          }
  13:      }
  14:      return default(T);
  15:  }




Vemos que el truco está en la línea 9, ahí llama al predicado y le pregunta si el item actual (dentro de la iteración) cumple con alguna condición que estará en el predicado. Vamos a ver una aplicación práctica

Ejemplo 2

Tenemos la siguiente entidad


   1:  class Persona
   2:  {
   3:      public int Edad;
   4:      public string Nombre;
   5:      public string Apellido;
   6:   
   7:      public Persona(string nombre, string apellido, int edad)
   8:      {
   9:          this.Edad = edad;
  10:          this.Apellido = apellido;
  11:          this.Nombre = nombre;
  12:      }
  13:   
  14:      public override string ToString()
  15:      {
  16:          return "Nombre=" + this.Nombre +" Apellido=" + this.Apellido +", Edad=" + this.Edad.ToString();
  17:      }
  18:  }

Ahora, creamos una lista de Persona y tratamos de recuperar, digamos, a mi.
 
   1:  List lista = new List(); 
   2:   
   3:  lista.Add(new Persona("Leonardo","Micheloni",31));
   4:  lista.Add(new Persona("Hernán", "Micheloni", 30));
   5:  lista.Add(new Persona("Adrián", "Micheloni", 34));
   6:   
   7:  Persona leonardo = lista.Find(new Predicate(delegate(Persona persona)
   8:  {
   9:      return persona.Nombre.Equals("Leonardo");
  10:  }));
  11:   
  12:  Console.WriteLine(leonardo);

Observemos este código, hasta la línea 6 se encuentra la creación de la lista de Persona, ahora en la línea 7 está el yeite. Primero invocamos al método Find de la lista genérica el cual espera un predicado, entonces lo declaramos como un método anónimo, simplemente le decimos que sea true si el nombre es igual a Leonardo, y listo, es todo.
Existe otro predicado predefinido en el framework que aparece en la lista genéricas, es el Action y se utiliza para aplicar una acción a los elementos de la colección.

Conclusión

Los delegados son elementos muy poderosos que nos permiten un grado de flexibilidad muy grande, recuerden el ejemplo de la lista, y todos los que se les ocurran; la utilidad de los métodos anónimos es grande también pero creo que no es bueno abusar porque queda un código algo confuso. Existe otra utilidad para los delegados que son los eventos pero eso será tema de otro post, hasta pronto.

8 comentarios:

Uberbandit dijo...

Hola,

muy bien la explicación de los delegados y los predicados, aunque menos mal no me he encontrado aún con ninguno de los últimos.

Por otra parte, revisa un poco el artículo pues tienes unos cuantos errores ortográficos y gramaticales que hacen bastante difícil su lectura.

Saludos

Leonardo Micheloni dijo...

Muchas gracias por el comentario. Por cierto, ¿a qué errores te referís?, lo revisé y está bastante bien (algun pifie con el teclado nomás). Será mi Español latino que te hace difícil la lectura?. De todos modos estoy intentando escribir un poco más "neutral" para que todos lo entiendan, gracias por la crítica.

þ@¢ø dijo...

asdfasdfasdfasdas

javi dijo...

Gracias Leo, me acabas de resolver un problemilla con las listas. Un saludo.

Javier

Leonardo Micheloni dijo...

Hola Javi, me alegra ver que te ha sido útil, un saludo.

Ricardo dijo...

Muchas gracias Javier por tu impecable explicación de los delegados, me interesaría saber si puedes explicar con esa sencillez el tema de delegados pero sobre Eventos, la verdad el tema me es muy confuso pero me sería de gran utilidad. Un saludo afectuoso

Ricardo

Leonardo Micheloni dijo...

Hola Ricardo, gracias por el comentario, como verás siempre estoy un paso adelante

http://leomicheloni.blogspot.com/search/label/eventos

fijate si te sirve, sino preguntame que cualquier duda en la que pueda ayudarte lo haré con gusto. Saudos.

ARTL dijo...

Muy buen articulo me aclaro mucho el uso de los delegados como callbacks, pero ahora no se si podras ayudarme con el ejemplo de los predicados,

Me da dos errores de compilacion
public T Find(Predicate match),

using the generic type 'EjemploDelegado.Predicate' requires '1' type
arguments

y el otro error

The Type or Namespace name 'T' could not be found(are you missing a using directive or an assembly reference?)

Muchas Gracias de antemano