¿Qué son los delegados en C++?

Delegados en C#: Qué son y cómo usarlos

13/05/2015

Valoración: 4.03 (3868 votos)

En el universo de la programación con C# y el framework .NET, existen conceptos fundamentales que, una vez dominados, abren un abanico de posibilidades para escribir código más limpio, flexible y potente. Uno de estos pilares es, sin duda, el delegado. Si vienes del mundo de C++, es posible que te suene el concepto de "puntero a función". Un delegado es la evolución de esa idea, pero con la robustez y seguridad de tipos que caracteriza a C#. Son la base para la gestión de eventos, la implementación de callbacks y una pieza clave en la magia detrás de LINQ. En este artículo, desglosaremos todo lo que necesitas saber para comprender y utilizar los delegados como un experto.

¿Qué son los delegados en C++?
Los delegados son como los punteros de función de C++, pero tienen seguridad de tipos. Los delegados permiten pasar los métodos como parámetros. Los delegados pueden usarse para definir métodos callback. Los delegados pueden encadenarse entre sí; por ejemplo, se puede llamar a varios métodos en un solo evento.
Índice de Contenido

¿Qué es Exactamente un Delegado en C#?

Para definirlo de forma sencilla, un delegado es un objeto que actúa como un intermediario o un "representante" de un método. Es un tipo de referencia que puede contener una referencia a un método con una firma específica (es decir, un conjunto particular de parámetros y un tipo de retorno). La clave aquí es la seguridad de tipos, o type-safe. A diferencia de los punteros a función de C++, un delegado solo puede apuntar a métodos que coincidan exactamente con la firma que él mismo define. Esto evita errores en tiempo de ejecución, ya que el compilador de C# se asegura de que la conexión sea válida desde el principio.

Imagina que tienes una tarea que puede ser realizada de varias maneras distintas. En lugar de escribir una lógica fija para llamar a un método específico, puedes usar un delegado como un parámetro. De esta forma, el método que llama no necesita saber qué implementación concreta se ejecutará; solo necesita saber que recibirá un "algo" que puede invocar y que cumplirá con un contrato (la firma del delegado).

Propiedades Clave de los Delegados

Para entender su poder, es útil resumir sus características principales:

  • Seguridad de Tipos (Type-Safe): Como mencionamos, el compilador verifica que la firma del método que se asigna al delegado coincida con la declaración del delegado.
  • Paso de Métodos como Parámetros: Permiten tratar a los métodos como si fueran variables. Puedes pasarlos a otras funciones, almacenarlos en colecciones o devolverlos desde otros métodos.
  • Definición de Métodos de Devolución de Llamada (Callbacks): Son ideales para escenarios donde una operación larga necesita notificar a otra parte del código cuando ha terminado. El método que realiza la operación invoca al delegado para "llamar de vuelta" al código original.
  • Encadenamiento (Multicasting): Un único delegado puede hacer referencia no a uno, sino a múltiples métodos. Al invocar el delegado, todos los métodos en su lista de invocación se ejecutarán en orden. Esto es fundamental para el sistema de eventos de .NET.

La Anatomía de un Delegado: Declaración y Sintaxis

Crear un delegado es un proceso de dos pasos: primero lo declaras (definiendo su firma) y luego creas una instancia de él, asociándolo a un método concreto.

La declaración utiliza la palabra clave delegate. Su estructura es muy similar a la de una firma de método:

// Declaración de un delegado que representa un método // que toma dos enteros y devuelve un entero. public delegate int OperacionMatematica(int a, int b);

En este ejemplo, hemos creado un nuevo tipo llamado OperacionMatematica. Cualquier método que acepte dos parámetros int y devuelva un int puede ser asignado a una variable de este tipo delegado.

Poniéndolo en Práctica: Instanciación y Uso

Una vez que tenemos nuestro tipo delegado declarado, podemos usarlo. Veamos un ejemplo completo. Supongamos que tenemos una clase `Calculadora` con métodos para sumar y restar.

// Métodos que cumplen con la firma del delegado public class Calculadora { public int Sumar(int a, int b) { Console.WriteLine($"Suma: {a + b}"); return a + b; } public int Restar(int a, int b) { Console.WriteLine($"Resta: {a - b}"); return a - b; } } // En nuestro método Main o donde lo necesitemos public static void Main() { Calculadora calc = new Calculadora(); // Instanciar el delegado y apuntarlo al método Sumar OperacionMatematica miOperacion = calc.Sumar; // Invocar el delegado. Esto ejecutará el método Sumar int resultadoSuma = miOperacion(10, 5); // Ahora, podemos reasignar el mismo delegado para que apunte a otro método miOperacion = calc.Restar; // Invocarlo de nuevo ejecutará el método Restar int resultadoResta = miOperacion(10, 5); }

Diferentes Formas de Instanciar un Delegado

C# ha evolucionado y ofrece varias maneras de crear instancias de delegados, desde las más explícitas hasta las más concisas y modernas.

  1. Instanciación Clásica (con `new`): Es la forma más antigua y verbosa, pero muy clara. Se pasa el método como argumento al constructor del delegado.
    OperacionMatematica op = new OperacionMatematica(calc.Sumar);
  2. Asignación Directa: El compilador es lo suficientemente inteligente como para inferir el tipo. Esta es la forma más común y legible para métodos con nombre.
    OperacionMatematica op = calc.Sumar;
  3. Métodos Anónimos: Introducidos en C# 2.0, permiten definir la implementación del método directamente en el lugar de la asignación, sin necesidad de crear un método separado con nombre. Se usa la palabra clave delegate.
    OperacionMatematica op = delegate(int a, int b) { return a * b; };
  4. Expresiones Lambda: La forma más moderna, concisa y poderosa, introducida en C# 3.0. Las expresiones lambda son la evolución de los métodos anónimos y son la base de LINQ.
    OperacionMatematica op = (a, b) => a * b;

Delegados Genéricos: `Action` y `Func` al Rescate

Declarar un tipo delegado personalizado para cada firma que necesites puede volverse repetitivo. Para solucionar esto, .NET proporciona dos familias de delegados genéricos que cubren la gran mayoría de los casos de uso: Action y Func.

¿Cómo declarar un delegado en C#?
Para declarar un delegado en C# se utiliza la palabra clave delegate, seguida de la firma del método que el delegado representará. delegate TipoRetorno MiDelegado(Tipo parametro1...); Por ejemplo, si queremos declarar un delegado que represente un método que no devuelve ningún valor y toma dos parámetros enteros, la declaración se vería así:

`Action`: Para Métodos sin Valor de Retorno (`void`)

El delegado Action se utiliza para encapsular métodos que no devuelven un valor. Existe en varias formas:

  • Action: Para un método sin parámetros.
  • Action<T>: Para un método con un parámetro de tipo T.
  • Action<T1, T2>: Para un método con dos parámetros.
  • ... y así sucesivamente, hasta 16 parámetros.
// Método que coincide con Actionpublic void MostrarInfo(string nombre, int edad) { Console.WriteLine($"Nombre: {nombre}, Edad: {edad}"); } // Uso del delegado Action Action<string, int> imprimirInfo = MostrarInfo; imprimirInfo("Ana", 30);

`Func`: Para Métodos que Devuelven un Valor

El delegado Func es similar, pero se usa para métodos que sí devuelven un valor. El último parámetro genérico siempre representa el tipo de retorno.

  • Func<TResult>: Para un método sin parámetros que devuelve un valor de tipo TResult.
  • Func<T, TResult>: Para un método con un parámetro de tipo T que devuelve TResult.
  • Func<T1, T2, TResult>: Para un método con dos parámetros que devuelve TResult.
  • ... y también hasta 16 parámetros de entrada.
// Método que coincide con Funcpublic double Multiplicar(double x, double y) { return x * y; } // Uso del delegado Func Func<double, double, double> operacionMultiplicar = Multiplicar; double resultado = operacionMultiplicar(5.5, 2.0); Console.WriteLine($"Resultado: {resultado}"); // Imprime 11

En la programación moderna de C#, casi siempre preferirás usar Action y Func en lugar de definir tus propios tipos de delegado, a menos que quieras darle un nombre semántico muy específico a tu delegado para mejorar la legibilidad del código (como en el caso de los eventos).

Tabla Comparativa de Métodos de Instanciación

MétodoSintaxis de EjemploVentajasCuándo Usarlo
Clásica (`new`)MiDelegado d = new MiDelegado(Metodo);Muy explícito, fácil de entender para principiantes.En código muy antiguo o cuando se requiere máxima claridad sobre la creación de un nuevo objeto delegado.
Asignación DirectaMiDelegado d = Metodo;Limpio, conciso y muy legible.Es la forma estándar de asignar un método con nombre a un delegado.
Método AnónimoMiDelegado d = delegate(int x) { ... };Permite definir lógica "in-situ" sin crear un método separado.Principalmente en código heredado. Las expresiones lambda son casi siempre una mejor opción.
Expresión LambdaMiDelegado d = x => { ... };Extremadamente conciso y potente. Permite el cierre de variables (closures).Es la opción preferida en C# moderno para cualquier lógica en línea, especialmente con LINQ y programación asíncrona.

Preguntas Frecuentes (FAQ)

¿Un delegado es lo mismo que un puntero a función en C++?

Son conceptualmente similares, pero la gran diferencia es la seguridad. Un delegado en C# es un objeto completamente gestionado y con seguridad de tipos. El compilador se asegura de que la firma coincida, previniendo errores que en C++ podrían causar comportamientos indefinidos o caídas del programa.

¿Cuándo debería usar `Action` o `Func` en lugar de un delegado personalizado?

Usa Action y Func en el 95% de los casos. Son estándar, reutilizables y evitan la proliferación de tipos innecesarios. Reserva la declaración de un delegado personalizado para situaciones en las que el nombre del tipo delegado añade un valor semántico importante, como en la definición de una API pública o en la declaración de eventos (ej. public delegate void PriceChangedHandler(decimal newPrice);).

¿Qué relación tienen los delegados con los eventos?

Los delegados son el mecanismo subyacente de los eventos en C#. Un evento es, en esencia, un delegado encapsulado que expone públicamente solo las operaciones de suscripción (+=) y desuscripción (-=), protegiendo la invocación para que solo pueda ser realizada desde dentro de la clase que lo declara.

¿Puedo usar delegados con métodos estáticos y de instancia?

Sí, absolutamente. Cuando un delegado referencia un método de instancia, el objeto delegado mantiene una referencia tanto al método como a la instancia del objeto sobre el que se debe invocar. Si referencia un método estático, solo almacena la referencia al método.

Si quieres conocer otros artículos parecidos a Delegados en C#: Qué son y cómo usarlos puedes visitar la categoría Juegos.

Subir