91 007 17 22info@asociacionaepi.es

Delegados en C#

Posteado en: blog Iniciado por

Delegados en C#

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?

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 voidSort(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.

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);
Muchas gracias
image_pdfimage_print

Leave a Reply

Uso de cookies

Utilizamos cookies propias y de terceros para mejorar la experiencia de navegación y ofrecer contenidos y publicidad de interés. Al continuar con la navegación entendemos que se acepta nuestra política de privacidad y cookies, pinche el enlace para mayor información.

ACEPTAR
Aviso de cookies