Delegados en C#. Ejemplo y código

Jul 18, 2012 Tutoriales C# 5 comentarios

La primera vez que oí hablar de delegados y eventos estaba en primer año, pero ese día falté a clases. Al principio no le presté mucho interés, porque nunca me había hecho falta, hasta que un día un amigo me la mostró como solución de un proyecto, y entonces me di cuenta de la cantidad de líneas de código que pude haber ahorrado si no hubiera faltado a clases ese día.

1. El Problema

Lo primero que te preguntas es que tipo de problema resuelven los delegados, o sea… para que debería aprender a usar delegados? Para responderte esta pregunta, lo mejor es ilustrarlo con un ejemplo:
Supongamos que tenemos el siguiente método de ordenación.

public void Sort(IComparable[] items){
   for(i=0; i<items.length; i++)
   {
      Icomparable tmp = items[i];
      items[i] = items[j];
      items[j] = tmp;
   }
}

El método Sort, nos permitirá ordenar cualquier cosa, que herede de la interfaz IComparable y que implemente el método CompareTo,

Por ejemplo, supongamos que tenemos la clase Persona definida de la siguiente forma:

//Definimos la clase persona
public class Persona: IComparable
{
   //Aqui usamos propiedades autoimplementadas (a partir de C# 3.0)
   //Fijate que no hay que definir primero las variables privadas
   public string Name { get; set; }
 
   public Date Birthday { get; set; }
 
   public float Height { get; set; }
 
   public Persona(string Name, Date Birthday, float Height)
   {
      this.Name = Name;
      this.Birthday = Birthday;
      this.Height = Height;
   }
 
   //Metodo CompareTo de la interfaz IComparable
   public int CompareTo(object obj)
   {
      Persona p = obj as Persona;
      //Comparamos las personas por Fecha de Nacimiento
      return this.Birthday.CompareTo(p.Birthday);
   }
}
//Clase Date que utilizamos en el campo Birthday de la clase Persona
public class Date : IComparable
{
   //Aqui usamos variables porque las propiedades Day, Month y Year
   //no tienen parte set
   int day, month, year;
 
   //Constructor de la clase
   public Date(int day, int month, int year)
   {
      this.day = day;
      this.month = month;
      this.year = year;
      if (!IsValid) throw new ArgumentException("Fecha no válida");
   }
 
   //Metodo para verificar si una fecha es valida
   bool IsValid
   {
      get{
         //Verifico si la fecha tiene rango valido
         if (year &lt;= 0 || month &lt;= 0 || month &gt; 12 || day &lt;= 0 || day &gt; 31)
            return false;
         //Verifico si el año es bisiesto que febrero no tenga mas de 28 dias
         if ((year % 400 == 0 && month == 2 && day > 28) || (month == 2 && day > 29))
            return false;
         // Verifico los meses que no tienen 31 dias
         if ((month == 4 || month == 6 || month == 9 || month == 11) &amp;&amp; day == 31)
            return false;
         return true;
      }
   }
 
   public int Day
   {
      get { return day; }
   }
   public int Month
   {
      get { return month; }
   }
   public int Year
   {
      get { return year; }
   }
 
   //Metodo para comparar fecha, nota que esta clase tambien hereda
   //de IComparable
   public int CompareTo(object x)
   {
      Date d = x as Date;
      if (d == null)
   	  throw new ArgumentException();
      // Uso operadores ternarios para comparar las fechas (asi me evito unos cuantos if else)
      return (d.Year > year || (d.Year == year && d.Month > month) || (d.Year == year && d.Month == month && d.Day > day)) ? -1 : (d.Year == year && d.Month == month && d.Day == day) ? 0 : 1;
   }
}
 
class Program
{
   //En el metodo Main es donde usamos las clases y
   //programamos algunos metodos que vayamos a utilizar
 
   static void Main(string[] args)
   {
      //Creo el array de personas que voy a ordenar
      Persona[] personas = new Persona[]
      {
         new Persona("Tom", new Date(1, 11, 1988), 169),
         new Persona("Jio", new Date(31, 8, 1990), 170),
         new Persona("Simone", new Date(7, 9, 1991), 165),
         new Persona("Ruben", new Date(22, 10, 1988), 165)
      };
 
      //Ordeno el array de personas
      Sort(personas);
 
      //Imprimo las personas despues de ordenarlas
      foreach (Persona p in personas)
      {
         Console.WriteLine("Nombre: {0}   Date: {1}, {2}, {3}, Height: {4}",
         p.Name, p.Birthday.Day, p.Birthday.Month, p.Birthday.Year, p.Height);
      }
      Console.ReadKey();
   }
 
   //Metodo de ordenacion sencillo
   //Puedes ver otros métodos de ordenación en post anteriores:
 
   static void Sort(IComparable[] items)
   {
      for (int i = 0; i < items.Length - 1; i++)
         for (int j = i + 1; j < items.Length; j++)
         //Comparamos las personas usando el metodo CompareTo que implementamos 				
         if (items[i].CompareTo(items[j]) > 0){
            IComparable tmp = items[i];
            items[i] = items[j];
            items[j] = tmp;
         }
   }
}

Como has visto hasta ahora, el método Sort se basa solo en el criterio definido por el método CompareTo de la clase que implemente IComparable (en este caso la clase Persona), pero que tal si quisiéramos tener diferentes criterios de ordenación para un mismo objeto? En nuestro caso, que tal si quisieramos ordenar un grupo de personas por orden alfabético? o por estatura?

2. Una alternativa

Para lograr esto se podría pensar en tener varios métodos de comparación dentro de la clase Persona, pero entonces ya el método Sort no nos serviría para comparar cualquier tipo que fuera IComparable, sino que la signatura sería static void Sort(Persona[] items) y por tanto tendríamos que escribir un método Sort para cada tipo de objeto que vayamos a ordenar.

El código cliente (el que está en el método Main) debería tener la posibilidad de escoger el criterio de comparación que desea utilizar, y es aquí donde el uso de delegados nos viene como anillo al dedo.

3. La solución usando delegados

Un delegado no es más que un tipo que recibe métodos con una signatura específica, al principo va a resultar un poco chocante si nunca te has visto en la necesidad de usarlo, porque no es muy común y muchos lenguajes no lo soportan, y la verdad a mi me tomó un poco de tiempo asimilar esto, así que si todavía no logras entender bien, te aconsejo que busques un poco más de información en el MSDN.

La signatura de los delegados viene siendo de la siguiente forma:

public delegate [valor de retorno] Comparison([parametro 1], [parametro 2], ... , [parametro n]);

En nuestro caso el delegado que vamos a utilizar es:

public delegate int Comparison(object x, object y);

que devuelve un entero y recibe dos objetos, esto quiere decir que este delegado recibirá como argumento cualquier método que devuelva un int y reciba dos parámetros de tipo object. A mi no me gusta mucho utlizar object (ya que tenemos genericidad) pero es mejor en este caso para ilustrar su utilidad.

Otra característica de los delegados es que lo podemos definir en cualquier parte del código, o sea, no tiene que estar dentro de una clase como los métodos, variables o propiedades, de hecho no tengo ni idea de como está implementado este tipo, pero apuesten a que voy a buscar después que termine este post…

Una vez que insertemos el delegado en cualquier parte de nuestro código, modificamos el método Sort para que lo use:

static void Sort(Persona[] items, Comparison compare)
{
   for (int i = 0; i < items.Length - 1; i++)
      for (int j = i + 1; j < items.Length; j++) 		
      //Comparamos usando el delegado 			
      if (compare(items[i],items[j]) > 0)
      {
         Persona tmp = items[i];
         items[i] = items[j];
         items[j] = tmp;
      }
}

Añadimos los métodos para comparar por nombre, por fecha de nacimiento y por altura a la clase Persona:

//Comparamos por nombre en orden alfabético
public static int CompareByName(object x, object y)
{
   Persona p1 = x as Persona;
   Persona p2 = y as Persona;
   //En C# se considera que una letra minuscula es menor que cualquier letra mayuscula
   //por eso tenemos que usar la propidad OrdinalIgnoreCase de la clase StringComparer
   return StringComparer.OrdinalIgnoreCase.Compare(p1.Name, p2.Name);
}
 
//Comparamos por altura de menor a mayor
public static int CompareByHeight(object x, object y)
{
   Persona p1 = x as Persona;
   Persona p2 = y as Persona;
   //El tipo float tiene un comparador por defecto, que es el que utilizamos
   return p1.Height.CompareTo(p2.Height);
}
 
//Comparamos por fecha de nacimiento como hacíamos en CompareTo
public static int CompareByBirthday(object x, object y)
{
   Persona p1 = x as Persona;
   Persona p2 = y as Persona;
   //El tipo float tiene un comparador por defecto, que es el que utilizamos
   return p1.Birthday.CompareTo(p2.Birthday);
}

Ahora veamos como utlizar estos en el código cliente:

static void Main(string[] args)
{
   Persona[] personas = new Persona[]
   {
      new Persona("Tom", new Date(1, 11, 1988), 169),
      new Persona("Jio", new Date(31, 8, 1990), 170),
      new Persona("Simone", new Date(7, 9, 1991), 165),
      new Persona("Ruben", new Date(22, 10, 1988), 165)
   };
 
   //Pasamos como parámetro al delegado el método CompareByName
   Sort(personas, Persona.CompareByName);
   //También se puede usar el nombre del método directamente sin pasarlo como parámetro
   //Sort(personas, new Comparison(Persona.CompareByName));
 
   Console.WriteLine("Ordenando las personas en orden alfabético");
   foreach (Persona p in personas)
   {
      Console.WriteLine("Nombre: {0}", p.Name);
   }
 
   Console.WriteLine();
   Console.WriteLine("Ordenando las personas por fecha de nacimiento");
   //Pasamos como parámetro al delegado el método CompareByBirthday
   Sort(personas, Persona.CompareByBirthday);
   foreach (Persona p in personas)
   {
      Console.WriteLine("Date: {0}, {1}, {2}",p.Birthday.Day, p.Birthday.Month, p.Birthday.Year);
   }
 
   Console.WriteLine();
   Console.WriteLine("Ordenando las personas por altura");
   //Pasamos como parámetro al delegado el método CompareByHeight
   Sort(personas, Persona.CompareByHeight);
   foreach (Persona p in personas)
   {
      Console.WriteLine("Height: {0}",p.Height);
   }
   Console.ReadKey();
}

Que tal si ahora nos piden que ordenemos a las personas por orden alfabético a partir de la segunda letra del nombre?
Solamente tendríamos que añadir el método:

//Comparamos por nombre en orden alfabético a partir d la segunda letra
public static int CompareFromSecondLetter(object x, object y)
{
   Persona p1 = x as Persona;
   Persona p2 = y as Persona;
   //Usamos substring para comparar los nombres a partir de la segunda letra
   return StringComparer.OrdinalIgnoreCase.Compare(p1.Name.Substring(1), p2.Name.Substring(1));
}

y usarlo como:

Sort(personas, Persona.CompareFromSecondLetter);

Pueden descargar todo el código del ejemplo desde aquí: Usando Delegados

Espero que hayan entendido bien el uso de delegados y como resolver un problema de diseño como el que les mostré en este post. Todavía falta mucho por hablar sobre los delegados, ya que tienen utilidad en otras muchas situaciones como métodos anónimos, LINQ y junto con la genericidad se pueden hacer maravillas, esto lo estaremos viendo en otro post. Por ahora practiquen y traten de dominar lo mejor posible esta característica.

Si tienen alguna duda, como siempre en los comentarios

Compartir:

Relacionados

algunos artículos que te pueden interesar

5 comentarios

Forma parte de nuestra discusión y síguela de cerca

quisiera que me ayudaran para resolver un programa de un laberinto que utilice metodo de recursividad
les qgradeceria mucho

Autor: @yancy | Fecha: Oct 1, 2012.

@yancy, mira en este mismo blog hay un post con el código sobre como resolver el problema del laberinto usando recursividad:
http://www.puntopeek.com/codigos-c/recursividad-con-c-3/

Autor: Tomy | Fecha: Ene 8, 2013.

Genial

Autor: manolo | Fecha: Ene 21, 2013.

gracias!

Autor: K1ke | Fecha: Ene 21, 2013.

Bueno, recien arranco con c# y justamente viendo unos videotutoriales me tope con el tema de los delegados, sigo estando desorientado ya que para alguien que empieza es dificil de encontrar donde implementarlo ya que no es algo que este en la mayoria de los lenguajes, voy a seguir buscando ejemplos a ver si aguno esta a un nivel mas basico ya que los ejemplos que vi hasta el momento si bien me orientaron un poco mas sigo sin entender la utilidad, en el videotutorial que vi lo usaron con webforms y no llego a apreciar cual es la diferencia a usar una variable. Este es el tuto si alguno lo mira y me podria indicar si estoy errando por mucho con el comportamiento de una variable o justo me tope con un ejemplo mal implementado (https://www.youtube.com/watch?v=ttf9lxO9mGY&index=6&list=PLAAhC8kCE0VlZtOyuO6NvFu-NRbrhbJAf)

Autor: Ulises | Fecha: Mar 24, 2015.

Escribe tu comentario

Requerido.

Requerido. No público.

Si tienes alguno.