Delegados, eventos y expresiones lambda
Los delegados
Un delegado es una especie de puente entre la llamada a un método y el método deseado. Los delegados se dividen en tipos e instancias. Un tipo delegado define el protocolo al que deben acogerse tanto el objeto que invoca como el método invocado. Esto incluye la lista de parámetros y el tipo de retorno, en una palabra, de la declaración. Una instancia de delegado es un objeto que hace referencia a uno o varios métodos que tienen una declaración conforme.
La declaración de un delegado se precede de la palabra clave delegate y la declaración solo contiene la declaración del método, como sucede con un miembro abstracto:
public delegate int Calculo(int i, int j);
Para crear una instancia de delegado, es suficiente con asignar un método cuya declaración sea conforme al delegado:
public class Clase1
{
public Clase1(int i, int j)
{
Calculo C = new Calculo(Adicion);
int result = C.Invoke(i, j);
}
public int Adicion(int i, int j)
{
return i + j;
}
}
En el ejemplo anterior, el método Adicion podría tener sobrecargas. En tal caso, el compilador tomaría automáticamente la sobrecarga correcta en función de la declaración del delegado al que se asigna el método.
La instrucción de instanciación del delegado en el ejemplo anterior se puede abreviar de la siguiente manera:
Calculo C = new Calculo(Adicion); // Notación completa
Calculo C = Adicion; ...
Los eventos
Trabajando con los delegados hay dos conceptos destacados: la difusión y la suscripción. El difusor es un tipo relacionado con un delegado. Es el que decide cuándo se debe invocar el delegado. El suscriptor es el conjunto de métodos relacionados con un delegado. Un suscriptor es totalmente independiente de los otros suscriptores, incluso dentro de un delegado. Los eventos formalizan este esquema.
La manera más sencilla de declarar un evento es añadir la palabra clave event antes de un miembro delegado. El evento Changed en la interface IReportChange se ha creado anteriormente de esta manera:
event EventHandler Changed;
El tipo que contiene el difusor tiene un acceso total a éste. Dicho acceso puede servir para añadir, eliminar o ejecutar métodos destino. Los otros tipos solo se podrán suscribir al evento con los operadores += y -=.
Analice el siguiente ejemplo, basado en el descriptor de acceso set de la propiedad hasChanged de la clase ProjectSettings:
set
{
if (this.hasChanged != value)
{
this.hasChanged = value;
if (this.Changed != null)
this.Changed(this, new EventArgs());
}
}
La clase ProjectSettings desencadena el evento Changed...
Las expresiones lambda
Una expresión lambda es un método sin nombre, que sustituye a una instancia de delegado. El compilador transforma una expresión lambda en un delegado.
La sintaxis de una expresión lambda es la siguiente:
(parámetro1, parámetro2, ...) => expresión o instrucciones
Si la expresión solo contiene un parámetro, los paréntesis se pueden omitir, como ocurre en el ejemplo de la siguiente sección.
1. El uso de las expresiones lambda
Tomemos el delegado siguiente:
public delegate int Multiplicador(int i);
Es posible asignar e invocar una expresión lambda como esta:
Multiplicador M = x => x * 2;
int i = M(10); // i = 20
El compilador resuelve una expresión lambda de este tipo creando un método privado y moviendo la expresión en este método.
Cada uno de los parámetros de la expresión lambda se corresponde con un parámetro del delegado y el tipo de expresión se corresponde con el tipo de retorno del delegado. En el ejemplo anterior, x se corresponde con el parámetro i del delegado y la expresión x * 2 con tipo de retorno del delegado.
Es posible abandonar uno de los argumentos de la expresión lambda usando el carácter _ (underscore o guion bajo). Esto es como tener una función con un argumento opcional:
List<Func<int, int, int>> multiplications =
new List<Func<int, int, int>>();
multiplications.Add((_, _) => 0 * 0);
multiplications.Add((int i, int _) => i * 0);
multiplications.Add((int i, int j) => i * j);
Una expresión lambda puede contener un bloque de instrucciones, en lugar de una expresión:
public delegate int ValorAbsoluto(int i);
ValorAbsoluto A = x =>
{
if (x < 0)
{
return -x;
}
else
{
return x;
}
};
int i = A(10); ...