Compartir a través de


Utilizar delegados (Guía de programación de C#)

Un delegado es un tipo que encapsula de forma segura un método, similar a un puntero de función en C y C++. A diferencia de los punteros de función de C, los delegados están orientados a objetos, proporcionan seguridad de tipos y son seguros. El tipo de un delegado se define por el nombre del delegado. En el ejemplo siguiente se declara un delegado denominado Del que puede encapsular un método que toma una cadena como argumento y devuelve void:

public delegate void Del(string message);

Normalmente, un objeto delegado se construye con el nombre del método que el delegado encapsulará o con un método anónimo. Una vez que se crea una instancia de delegado, el delegado pasará al método una llamada de método realizada al delegado. Los parámetros pasados al delegado por el autor de la llamada se pasan a su vez al método, y el valor devuelto desde el método, si lo hubiera, es devuelto por el delegado al autor de la llamada. Esto se conoce como invocar al delegado. Un delegado con instancias se puede invocar como si fuera el propio método encapsulado. Por ejemplo:

// Create a method for a delegate. 
public static void DelegateMethod(string message)
{
    System.Console.WriteLine(message);
}
// Instantiate the delegate.
Del handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

Los tipos de delegado se derivan de la clase Delegate en .NET Framework. Los tipos de delegados son sealed —no se puede derivar— y no es posible derivar clases personalizadas de Delegate. Dado que el delegado con instancias es un objeto, puede pasarse como parámetro o asignarse a una propiedad. De este modo, un método puede aceptar un delegado como parámetro y llamar al delegado en algún momento posterior. Esto se conoce como devolución de llamada asincrónica y es un método común para notificar a un llamador que un proceso largo ha finalizado. Cuando se utiliza un delegado de esta manera, el código que usa al delegado no necesita ningún conocimiento de la implementación del método empleado. La funcionalidad es similar a la encapsulación que proporcionan las interfaces.

Otro uso común de devoluciones de llamada es definir un método de comparación personalizado y pasar ese delegado a un método de ordenación. Permite que el código del llamador se convierta en parte del algoritmo de ordenación. En el siguiente método de ejemplo se usa el tipo Del como parámetro:

public void MethodWithCallback(int param1, int param2, Del callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

Luego puede pasar el delegado creado anteriormente a ese método:

MethodWithCallback(1, 2, handler);

Y recibir los siguientes resultados en la consola:

The number is: 3

Si se usa el delegado como abstracción, no es necesario que MethodWithCallback llame directamente a la consola —no tiene que estar diseñado pensando en una consola—. Lo que MethodWithCallback hace es simplemente preparar una cadena y pasarla a otro método. Esto es especialmente eficaz, puesto que un método delegado puede utilizar cualquier número de parámetros.

Cuando se crea un delegado para encapsular un método de instancia, el delegado hace referencia tanto a la instancia como al método. Un delegado no tiene conocimiento del tipo de instancia —aparte del método al que encapsula—, por lo que un delegado puede hacer referencia a cualquier tipo de objeto siempre que haya un método en ese objeto que coincida con la signatura del delegado. Cuando se crea un delegado para encapsular un método estático, solo hace referencia al método. Considere las siguientes declaraciones:

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

Junto con el DelegateMethod estático mostrado anteriormente, ahora tenemos tres métodos que se pueden encapsularse mediante una instancia de Del.

Un delegado puede llamar a más de un método cuando se invoca. Esto se conoce como multidifusión. Para agregar un método adicional a la lista de métodos del delegado —la lista de invocación—, simplemente es necesario agregar dos delegados mediante los operadores de adición o asignación y suma ('+' o '+='). Por ejemplo:

MethodClass obj = new MethodClass();
Del d1 = obj.Method1;
Del d2 = obj.Method2;
Del d3 = DelegateMethod;

//Both types of assignment are valid.
Del allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

En este momento allMethodsDelegate contiene tres métodos en su lista de invocación: Method1, Method2 y DelegateMethod. Los tres delegados originales, d1, d2 y d3, permanecen sin cambios. Cuando se invoca allMethodsDelegate, todos los métodos se llaman en orden. Si el delegado usa parámetros de referencia, la referencia se pasa secuencialmente a cada uno de los tres métodos por turnos, y cualquier cambio que realice un método es visible para el siguiente método. Cuando alguno de los métodos produce una excepción que no se captura dentro del método, esa excepción se pasa al llamador del delegado y no se llama a los métodos siguientes de la lista de invocación. Si el delegado tiene un valor devuelto o los parámetros de salida, devuelve el valor devuelto y los parámetros del último método invocado. Para quitar un método de la lista de invocación, utilice el operador de decremento o el operador de asignación de decremento ('-' o '-= '). Por ejemplo:

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Del oneMethodDelegate = allMethodsDelegate - d2;

Dado que los tipos de delegado se derivan de System.Delegate, los métodos y propiedades definidas por esa clase se pueden llamar en el delegado. Por ejemplo, para buscar el número de métodos en la lista de invocación de un delegado, se puede escribir:

int invocationCount = d1.GetInvocationList().GetLength(0);

Los delegados con más de un método en su lista de invocación derivan de MulticastDelegate, que es una subclase de System.Delegate. El código anterior funciona en ambos casos porque las dos clases admiten GetInvocationList.

Los delegados de multidifusión se utilizan mucho en el control de eventos. Los objetos de origen de evento envían notificaciones de evento a los objetos de destinatario que se han registrado para recibir ese evento. Para suscribirse a un evento, el destinatario crea un método diseñado para controlar el evento; a continuación, crea a un delegado para dicho método y pasa el delegado al origen de eventos. El origen llama al delegado cuando se produce el evento. Después, el delegado llama al método que controla los eventos en el destinatario y entrega los datos del evento. El origen del evento define el tipo de delegado para un evento determinado. Para más información, vea Eventos (Guía de programación de C#).

La comparación de delegados de dos tipos distintos asignados en tiempo de compilación generará un error de compilación. Si las instancias de delegado son estáticamente del tipo System.Delegate, entonces se permite la comparación, pero devolverá false en tiempo de ejecución. Por ejemplo:

delegate void Delegate1();
delegate void Delegate2();

static void method(Delegate1 d, Delegate2 e, System.Delegate f)
{
    // Compile-time error. 
    //Console.WriteLine(d == e); 

    // OK at compile-time. False if the run-time type of f  
    // is not the same as that of d.
    System.Console.WriteLine(d == f);
}

Vea también

Referencia

Delegados (Guía de programación de C#)

Utilizar varianza en delegados (C# y Visual Basic)

Usar la varianza para los delegados genéricos Func y Action (C# y Visual Basic)

Eventos (Guía de programación de C#)

Conceptos

Guía de programación de C#

Varianza en delegados (C# y Visual Basic)