Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
System.Delegate y la palabra clave
En este artículo se describen las clases de .NET que admiten delegados y cómo se asignan a la delegate palabra clave .
¿Qué son los delegados?
Piense en un delegado como una manera de almacenar una referencia a un método, similar a cómo puede almacenar una referencia a un objeto. Al igual que puede pasar objetos a métodos, también puede utilizar delegados para pasar referencias de métodos. Esto resulta útil cuando se quiere escribir código flexible en el que se pueden "conectar" distintos métodos para proporcionar comportamientos diferentes.
Por ejemplo, imagine que tiene una calculadora que puede realizar operaciones en dos números. En lugar de codificar de forma rígida, suma, multiplicación y división en métodos independientes, podría usar delegados para representar cualquier operación que tome dos números y devuelva un resultado.
Definición de tipos de delegado
Ahora vamos a ver cómo crear tipos delegados mediante la delegate palabra clave . Al definir un tipo de delegado, básicamente va a crear una plantilla que describa qué tipo de métodos se pueden almacenar en ese delegado.
Defina un tipo de delegado con sintaxis similar a una firma de método, pero con la delegate palabra clave al principio:
// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);
Este Calculator delegado puede contener referencias a cualquier método que tome dos int parámetros y devuelva un int.
Veamos un ejemplo más práctico. Cuando quiera ordenar una lista, debe indicar al algoritmo de ordenación cómo comparar elementos. Veamos cómo ayudan los delegados con el método List.Sort(). El primer paso es crear un tipo de delegado para la operación de comparación:
// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);
Este Comparison<T> delegado puede almacenar referencias a cualquier método que:
- Toma dos parámetros de tipo
T - Devuelve un
intvalor (normalmente -1, 0 o 1 para indicar "menor que", "igual a" o "mayor que")
Al definir un tipo de delegado como este, el compilador genera automáticamente una clase derivada de System.Delegate que coincide con la firma. Esta clase gestiona toda la complejidad del almacenamiento y la invocación de las referencias de método.
El Comparison tipo delegado es un tipo genérico, lo que significa que puede funcionar con cualquier tipo T. Para obtener más información sobre los genéricos, vea Clases y métodos genéricos.
Observe que aunque la sintaxis es similar a declarar una variable, realmente declara un nuevo tipo. Puede definir tipos delegados dentro de clases, directamente dentro de espacios de nombres o incluso en el espacio de nombres global.
Nota:
No se recomienda declarar tipos delegados (u otros tipos) directamente en el espacio de nombres global.
El compilador también genera controladores de adición y eliminación para este nuevo tipo para que los clientes de esta clase puedan agregar y quitar métodos de la lista de invocación de una instancia. El compilador aplica que la firma del método que se va a agregar o quitar coincide con la firma usada al declarar el tipo de delegado.
Declaración de instancias de delegados
Después de definir el tipo de delegado, puede crear instancias (variables) de ese tipo. Piense en esto como crear un "espacio" en el que puede almacenar una referencia a un método.
Al igual que todas las variables de C#, no se pueden declarar instancias de delegado directamente en un espacio de nombres o en el espacio de nombres global.
// Inside a class definition:
public Comparison<T> comparator;
El tipo de esta variable es Comparison<T> (el tipo delegado que definió anteriormente) y el nombre de la variable es comparator. En este momento, comparator aún no apunta a ningún método, es como una ranura vacía que espera a rellenarse.
También puede declarar variables de delegado como variables locales o parámetros de método, al igual que cualquier otro tipo de variable.
Invocación de delegados
Una vez que tenga una instancia de delegado que apunte a un método, puede invocar (llamar a) ese método a través del delegado. Se invocan los métodos que se encuentran en la lista de invocación de un delegado llamando a ese delegado como si fuera un método.
Este es el modo en que el método usa el Sort() delegado de comparación para determinar el orden de los objetos:
int result = comparator(left, right);
En esta línea, el código invoca el método adjunto al delegado. Trata la variable de delegado como si fuera un nombre de método y la llame mediante la sintaxis de llamada de método normal.
Sin embargo, esta línea de código hace una suposición no segura: supone que se ha agregado un método de destino al delegado. Si no se ha asociado ningún método, la línea anterior haría que se produjese una NullReferenceException. Los patrones que se usan para solucionar este problema son más sofisticados que una comprobación de valores NULL simple y se tratan más adelante en esta serie.
Asignar, agregar y quitar destinos de invocación
Ahora sabe cómo definir tipos de delegado, declarar instancias de delegado e invocar delegados. ¿Pero cómo se conecta realmente un método a un delegado? Aquí es donde entra en acción la asignación de delegados.
Para utilizar un delegado, necesita asignarle un método. El método que asigne debe tener la misma firma (mismos parámetros y tipo de valor devuelto) que define el tipo de delegado.
Veamos un ejemplo práctico. Supongamos que desea ordenar una lista de cadenas por su longitud. Debe crear un método de comparación que coincida con la Comparison<string> firma del delegado:
private static int CompareLength(string left, string right) =>
left.Length.CompareTo(right.Length);
Este método toma dos cadenas y devuelve un entero que indica qué cadena es "mayor" (más larga en este caso). El método se declara como privado, que es perfectamente correcto. No es necesario que el método forme parte de la interfaz pública para usarlo con un delegado.
Ahora puede pasar este método al List.Sort() método :
phrases.Sort(CompareLength);
Observe que usa el nombre del método sin paréntesis. Esto indica al compilador que convierta la referencia del método en un delegado que se pueda invocar más adelante. El Sort() método llamará al CompareLength método siempre que necesite comparar dos cadenas.
También puede ser más explícito declarando una variable de delegado y asignando el método a ella:
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
Ambos enfoques logran lo mismo. El primer enfoque es más conciso, mientras que el segundo hace que la asignación de delegados sea más explícita.
Para los métodos simples, es habitual usar expresiones lambda en lugar de definir un método independiente:
Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);
Las expresiones lambda proporcionan una manera compacta de definir métodos simples en línea. El uso de expresiones lambda para destinos delegados se trata con más detalle en una sección posterior.
En los ejemplos hasta ahora se muestran delegados con un único método de destino. Sin embargo, los objetos delegados pueden admitir listas de invocación que tienen varios métodos de destino asociados a un único objeto delegado. Esta funcionalidad es especialmente útil para escenarios de control de eventos.
Clases Delegate y MulticastDelegate
En segundo plano, las características de delegado que ha usado se basan en dos clases clave en .NET Framework: Delegate y MulticastDelegate. Normalmente no se trabaja directamente con estas clases, pero proporcionan la base que permite el funcionamiento de los delegados.
La System.Delegate clase y su subclase System.MulticastDelegate directa proporcionan compatibilidad con el marco para crear delegados, registrar métodos como destinos delegados e invocar todos los métodos registrados con un delegado.
Este es un detalle de diseño interesante: System.Delegate y System.MulticastDelegate no son tipos delegados que puede usar. En su lugar, sirven como clases base para todos los tipos de delegado específicos que cree. El lenguaje C# impide que herede directamente de estas clases; debe usar la delegate palabra clave en su lugar.
Cuando se usa la delegate palabra clave para declarar un tipo de delegado, el compilador de C# crea automáticamente una clase derivada de MulticastDelegate con la firma específica.
¿Por qué este diseño?
Este diseño tiene sus raíces en la primera versión de C# y .NET. El equipo de diseño tenía varios objetivos:
Seguridad de tipo: el equipo quería asegurarse de que el lenguaje aplicaba la seguridad de tipo al usar delegados. Esto significa asegurarse de que los delegados se invocan con el tipo correcto y el número correcto de argumentos, y que los tipos devueltos se verifican correctamente en tiempo de compilación.
Rendimiento: al hacer que el compilador genere clases de delegados concretas que representan firmas de método específicas, el entorno de ejecución puede optimizar las invocaciones de delegados.
Simplicidad: los delegados se incluyeron en la versión 1.0 de .NET, que era anterior a la introducción de genéricos. El diseño necesitaba funcionar dentro de las limitaciones de la época.
La solución fue hacer que el compilador creara las clases de delegados concretas que coincidieran con las firmas de método, lo que garantiza la seguridad de tipos mientras oculta la complejidad para usted.
Trabajar con métodos delegados
Aunque no puede crear clases derivadas directamente, en ocasiones usará métodos definidos en las Delegate clases y MulticastDelegate . Aquí están los más importantes a tener en cuenta.
Cada delegado con el que trabaja se deriva de MulticastDelegate. Un delegado "multidifusión" significa que se puede invocar más de un destino de método al llamar a través de un delegado. El diseño original consideró la distinción entre delegados que solo podían invocar un método frente a delegados que podían invocar varios métodos. En la práctica, esta distinción resultó menos útil de lo que se pensó originalmente, por lo que todos los delegados de .NET admiten varios métodos objetivo.
Los métodos más usados al trabajar con delegados son:
-
Invoke(): llama a todos los métodos asociados al delegado. BeginInvoke()/EndInvoke(): se usa para patrones de invocación asincrónica (aunqueasync/awaitahora es preferible)
En la mayoría de los casos, no llamará directamente a estos métodos. En su lugar, usará la sintaxis de llamada al método en la variable delegada, como se muestra en los ejemplos anteriores. Sin embargo, como verá más adelante en esta serie, hay patrones que funcionan directamente con estos métodos.
Resumen
Ahora que ha visto cómo la sintaxis del lenguaje C# se asigna a las clases .NET subyacentes, está listo para explorar cómo se utilizan, crean e invocan los delegados fuertemente tipificados en escenarios más complejos.