Examen del enlace dinámico con delegados

Completado

En C#, los métodos se usan para realizar acciones, como realizar un cálculo o recuperar información. Las aplicaciones utilizan métodos para lograr los resultados previstos y llaman a los métodos cuando se cumplen condiciones específicas. Las aplicaciones suelen automatizar los procesos al tener un método llamado a otro, que podría llamar al tercer método, etc. Dado que las condiciones se conocen en tiempo de compilación, el código ejecutable implementa flujos de trabajo predefinidos de forma predecible. Por ejemplo, puede tener un método que genere un informe de cliente mensual cuando se haga clic en un botón. Cuando se ejecuta el código y se hace clic en el botón, se llama al método y se genera el informe. Este escenario es un ejemplo de enlace anticipado, donde el método al que se va a llamar se determina en tiempo de compilación. El enlace anticipado es excelente para muchos escenarios, ya que facilita la lectura y comprensión del código. Puede ver el flujo del programa siguiendo las llamadas al método . El código es predecible y sabe exactamente qué método se llama cuando se hace clic en el botón.

Sin embargo, hay ocasiones en las que no se sabe a qué método llamar hasta que se ejecuta el programa. Por ejemplo, supongamos que está trabajando en una aplicación que se basa en dependencias en tiempo de ejecución para decidir qué método es necesario para analizar los datos del cliente. Este escenario es un ejemplo de ligadura tardía, donde el método al que se va a llamar se determina durante la ejecución. En este caso, el método al que se va a llamar puede depender de una entrada de usuario de recursos externos o del estado actual de la aplicación.

En C#, los delegados se usan para implementar el enlace dinámico.

¿Qué es un delegado?

Un delegado es un tipo de .NET derivado de la Delegate clase y que se usa para encapsular métodos. Al encapsular métodos y crear instancias de ellos como objetos, los delegados permiten almacenar métodos en variables, pasarlos como argumentos a otros métodos e invocarlos más adelante.

La firma de un delegado define los parámetros y el tipo de valor devuelto de los métodos que se pueden asignar a él. Esto significa que puede crear un tipo de delegado que coincida con la firma de un método y, a continuación, asignar cualquier método con esa firma al delegado. Esta funcionalidad le permite llamar al método a través del delegado, incluso si no sabe qué método es en tiempo de compilación.

En el ejemplo siguiente se muestra cómo definir un tipo de delegado:


public delegate int PerformCalculation(int x, int y);

En este ejemplo se define un delegado denominado PerformCalculation. El PerformCalculation delegado se define para representar métodos que toman dos int parámetros (x e y) y devuelven un int. Este delegado se puede usar para encapsular métodos que realizan cálculos diferentes, como suma, resta, multiplicación, división o ecuaciones más complejas que usan los dos parámetros.

Por ejemplo, este delegado podría usarse para encapsular los métodos siguientes:


public class Calculator
{
    public int Add(int x, int y)
    {
        return x + y;
    }

    public int Subtract(int x, int y)
    {
        return x - y;
    }

    public int Multiply(int x, int y)
    {
        return x * y;
    }

    public int Divide(int x, int y)
    {
        if (y == 0)
            throw new DivideByZeroException();
        return x / y;
    }
}

En este ejemplo se define una Calculator clase que contiene métodos que coinciden con la firma del PerformCalculation delegado. Puede crear instancias del delegado y asignarlas a distintos métodos, lo que le permite llamar al método adecuado en tiempo de ejecución en función de sus necesidades.

En el código siguiente se muestra cómo puede utilizar el delegado PerformCalculation:


public class Program
{
    public static void Main()
    {
        Calculator calculator = new Calculator();

        // Create delegate instances
        PerformCalculation add = new PerformCalculation(calculator.Add);
        PerformCalculation subtract = new PerformCalculation(calculator.Subtract);
        PerformCalculation multiply = new PerformCalculation(calculator.Multiply);
        PerformCalculation divide = new PerformCalculation(calculator.Divide);

        // Call the methods using the delegates
        Console.WriteLine("Addition: " + add(5, 3)); // Output: 8
        Console.WriteLine("Subtraction: " + subtract(5, 3)); // Output: 2
        Console.WriteLine("Multiplication: " + multiply(5, 3)); // Output: 15
        Console.WriteLine("Division: " + divide(5, 3)); // Output: 1
    }
}

En este ejemplo se crea una instancia de la Calculator clase y se asignan los métodos a instancias del PerformCalculation delegado. A continuación, se llama a los métodos mediante las instancias de delegado, lo que le permite realizar cálculos diferentes en función del delegado asignado.

Características de tipo de delegado

Los delegados son tipos de referencia, lo que significa que se almacenan en el montón y se pueden pasar como cualquier otro objeto. Al crear una instancia de delegado, está creando un objeto que encapsula un método. Esta encapsulación permite tratar el delegado como un objeto de primera clase, al igual que cualquier otro objeto de C#. Puede crear instancias de delegados, asignarlas a variables y pasarlas como argumentos a métodos. El proceso es similar a cómo se trabaja con otros objetos en C#, como cadenas o listas.

Los tipos delegados están sellados, no se pueden derivar de y no es posible derivar clases personalizadas de la Delegate clase . Los delegados también son inmutables, lo que significa que una vez que se ha creado un delegado, no se puede cambiar el método que encapsula. Sin embargo, puede crear nuevas instancias de delegado que apunten a métodos diferentes. Esta inmutabilidad garantiza que el delegado siempre apunte al mismo método, lo que es importante para mantener la integridad del código.

Puede llamar a una instancia de un delegado como si fuera un método e invoca el método que encapsula.

Los delegados tienen las siguientes características:

  • Los delegados le permiten pasar métodos como argumentos a otros métodos.
  • Los delegados se pueden encadenar, como al llamar a varios métodos en un solo evento.
  • Los delegados cuentan con seguridad de tipos, lo que significa que el compilador comprueba que una firma de método coincide con la firma del delegado durante la compilación.

Nota:

Los métodos no tienen que coincidir exactamente con el tipo de delegado. Si la firma del método es compatible, el compilador permite asignar el método al delegado. Este comportamiento se conoce como covarianza y contravarianza. La Covarianza permite usar un tipo más derivado que el especificado originalmente, mientras que la contravarianza permite usar un tipo menos derivado. Esta funcionalidad es útil al trabajar con herencia y polimorfismo, ya que permite crear código más flexible y reutilizable. La varianza se examina en una unidad independiente de este módulo.

¿Por qué usar delegados?

Los delegados ofrecen varias ventajas sobre las llamadas directas a métodos y resuelven importantes problemas relacionados con la flexibilidad del código, la invocación dinámica de métodos y la seguridad de tipos.

Los delegados proporcionan muchas ventajas, incluidos los siguientes elementos:

  • Flexibilidad: los delegados permiten pasar métodos diferentes como parámetros, lo que permite el comportamiento dinámico en función de las condiciones del tiempo de ejecución. Esta flexibilidad es útil cuando la operación exacta que se va a realizar se determina en tiempo de ejecución.
  • Extensibilidad: con delegados, puede ampliar fácilmente la funcionalidad agregando nuevas operaciones sin modificar el código existente. Puede pasar cualquier método que coincida con la firma del delegado.
  • Desacoplamiento: Los delegados desacoplan la invocación de un método de su definición, lo que hace que el código sea más modular y fácil de mantener.

Los delegados son una característica eficaz de C# que resuelve varios problemas comunes en la programación. Proporcionan una manera flexible y con seguridad de tipos para encapsular métodos, lo que permite la invocación de métodos dinámicos, métodos de devolución de llamada e invocación de multidifusión. Mediante el uso de delegados, puede escribir código más reutilizable y fácil de mantener que pueda adaptarse a los requisitos cambiantes.

Invocación de método dinámico

La capacidad de invocar métodos dinámicamente en tiempo de ejecución es una característica eficaz de los delegados. La invocación de método dinámico permite escribir código más flexible y reutilizable, ya que puede pasar diferentes métodos como parámetros sin conocer su implementación exacta en tiempo de compilación.

Escenario: imagine que tiene una lista de clientes y necesita ordenarlos por criterios diferentes, como el nombre, el tipo de cuenta o el identificador de cliente. Sin delegados, tendría que escribir métodos de ordenación independientes para cada criterio, lo que conduce a código repetitivo y menos fácil de mantener.

Solución: los delegados permiten pasar métodos como parámetros, lo que permite la invocación de método dinámico. Puede definir un delegado que tenga la misma firma que el método de ordenación y pasar diferentes funciones de comparación durante la ejecución. Esto hace que el código sea más flexible y reutilizable.

Métodos de devolución de llamada

La necesidad de métodos de devolución de llamada surge en escenarios en los que se quiere realizar una acción una vez que se complete una operación determinada. El patrón de devolución de llamada es común en la programación asincrónica, donde es posible que desee notificar al usuario o realizar otras acciones una vez finalizada una tarea de larga duración.

Escenario: en la programación asincrónica, a menudo es necesario realizar otras acciones una vez completada una operación. Sin delegados, tendría que acoplar estrechamente la operación asincrónica con las acciones de seguimiento, lo que reduce la flexibilidad y dificulta el mantenimiento del código.

Solución: Los delegados habilitan la implementación de métodos de devolución de llamada, lo que le permite especificar las acciones de seguimiento dinámicamente. Esto desacopla la operación asincrónica de la lógica de devolución de llamada, lo que mejora la flexibilidad y el mantenimiento.

Seguridad de tipos

La seguridad de tipos es un aspecto fundamental de la programación que garantiza que los métodos que invoca coincidan con las firmas esperadas. Esto evita errores en tiempo de ejecución y hace que el código sea más sólido.

Escenario: al pasar métodos como parámetros o almacenarlos para la invocación posterior, debe asegurarse de que las firmas del método coinciden. Sin delegados, carecería de seguridad de tipos, lo que provocaría posibles errores en tiempo de ejecución y código menos sólido.

Solución: Los delegados proporcionan referencias a métodos que son seguros en cuanto a tipos, garantizando que las firmas de los métodos coincidan con la firma del delegado. Esto evita errores en tiempo de ejecución y hace que el código sea más sólido.

Invocación de multidifusión

La invocación de multidifusión es una característica de los delegados que permite invocar varios métodos con un solo delegado. Esta característica es útil en escenarios de gestión de eventos en los que desea notificar a varios suscriptores de eventos sobre una ocurrencia.

Escenario: en el manejo de eventos, a menudo es necesario notificar a varios suscriptores sobre un acontecimiento. Sin delegados, tendría que administrar manualmente la lista de suscriptores e invocar sus métodos, lo que conduce a código complejo y propenso a errores.

Solución: Los delegados admiten la invocación de multidifusión, lo que permite que un único delegado haga referencia a varios métodos. Esto simplifica el control de eventos mediante la administración automática de la lista de suscriptores y la invocación de sus métodos en secuencia.

Procedimientos recomendados para declarar delegados en C#

Al agregar delegados a una clase, es importante seguir los procedimientos recomendados para asegurarse de que el código está organizado, legible y fácil de mantener. Estas son algunas instrucciones sobre dónde definir delegados:

Definir delegados en la parte superior de un archivo de clase

Es habitual que los desarrolladores declaren delegados en la parte superior de un archivo, normalmente dentro del espacio de nombres, pero fuera de cualquier clase. Este enfoque facilita la localización de las definiciones de delegado y su propósito. También permite definir delegados que pueden usar varias clases dentro del mismo espacio de nombres.

El código siguiente muestra cómo implementar un delegado definido fuera de una clase:


namespace MyNamespace

// Define the delegate outside the class
public delegate void MyDelegate(string message);

public class Publisher
{
    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        MyDelegate del = new MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}

Definir delegados dentro de una clase

Si un delegado es específico de una clase y no está pensado para usarse fuera de la clase, puede definir el delegado dentro de la clase . Esto encapsula el delegado dentro de la clase , que es útil cuando el delegado está estrechamente relacionado con la funcionalidad de la clase y no está pensado para su uso fuera de ese contexto. Si el delegado es público, otras clases del mismo espacio de nombres pueden tener acceso al delegado mediante una instancia de la clase .


namespace MyNamespace

public class Publisher
{
    // Define a public delegate
    public delegate void MyDelegate(string message);

    // Method that uses the delegate
    public void PublishMessage(MyDelegate del)
    {
        del("Hello from Publisher!");
    }
}

public class Subscriber
{
    public void Subscribe()
    {
        // Create an instance of the Publisher class
        Publisher publisher = new Publisher();

        // Create an instance of the delegate and pass a method to it
        Publisher.MyDelegate del = new Publisher.MyDelegate(PrintMessage);

        // Call the method of the Publisher class and pass the delegate
        publisher.PublishMessage(del);
    }

    // Method that matches the delegate signature
    public void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
}


Puntos clave

  • Los delegados son tipos de .NET derivados de la Delegate clase que encapsula métodos.
  • Los delegados permiten pasar métodos como parámetros, almacenarlos en variables e invocarlos en tiempo de ejecución.
  • Los delegados cuentan con seguridad de tipos, lo que significa que el compilador comprueba que las firmas de método coincidan con la firma del delegado durante la compilación.
  • Los delegados se pueden utilizar para la invocación dinámica de métodos, métodos de devolución de llamada, seguridad de tipos e invocación de multidifusión.
  • Los delegados son la base para el control de eventos en C#.
  • Los delegados se pueden encadenar juntos, lo que permite invocar varios métodos con un único delegado.
  • Normalmente, los delegados se definen fuera de una clase para habilitar el acceso desde otras clases sin necesidad de crear instancias de la clase.