Desde que empecé a trabajar con C#, una de las cosas que más me llamó la atención fue la potencia y flexibilidad que ofrece el lenguaje para manejar comportamientos dinámicos. Una de esas herramientas es el uso de delegados (o delegates en inglés) y el patrón de delegación, a veces llamado Delegator.
Un delegate en C# es un tipo seguro que hace referencia a un método con una firma y tipo de retorno específicos. Dicho de otra forma, es como un puntero a función que se puede pasar como parámetro, almacenar en variables o devolver desde otros métodos.
En mi experiencia, los delegates son muy útiles cuando queremos que una clase o método pueda ejecutar dinámicamente diferentes comportamientos sin conocer de antemano qué método se ejecutará.
Te muestro cómo se declara y usa un delegate simple:
// Declaración del delegate
public delegate void SaludoDelegate(string nombre);
// Método que coincide con la firma del delegate
public static void Saludar(string nombre)
{
Console.WriteLine($"¡Hola, {nombre}!");
}
// Uso del delegate
SaludoDelegate saludo = new SaludoDelegate(Saludar);
saludo("Carlos");
Así de sencillo: declaras el delegate, creas una instancia que apunte a un método compatible y lo invocas. 🔥
Aquí es donde viene la confusión habitual. C# no tiene una clase Delegator
como tal, sino que implementa el patrón Delegation usando delegates, interfaces o composición. El patrón Delegator consiste en una clase que no implementa directamente cierta funcionalidad, sino que delega su ejecución a otra clase.
Me ha tocado usar este patrón cuando quiero encapsular un comportamiento, pero permitiendo que otro objeto especializado lo ejecute.
Te comparto un ejemplo que he usado en una app para separar responsabilidades:
public interface ILogger
{
void Log(string mensaje);
}
public class ConsoleLogger : ILogger
{
public void Log(string mensaje)
{
Console.WriteLine($"[Console] {mensaje}");
}
}
public class FileLogger : ILogger
{
public void Log(string mensaje)
{
// Aquí iría la lógica para escribir en archivo
Console.WriteLine($"[Archivo] {mensaje}");
}
}
public class Aplicacion
{
private ILogger _logger;
public Aplicacion(ILogger logger)
{
_logger = logger;
}
public void Ejecutar()
{
_logger.Log("La aplicación está corriendo...");
}
}
// Uso
var app = new Aplicacion(new ConsoleLogger());
app.Ejecutar();
En este caso, Aplicacion no decide cómo se hace el Log
, sino que delega esa responsabilidad a una clase que implemente ILogger
. Este es el corazón del patrón Delegator en C#. ⚙️✨
Hay situaciones donde no quiero crear una clase completa o interfaz para delegar comportamiento, sino que me basta con pasar métodos como parámetros. Aquí es donde delegates y Action/Func brillan.
Action
y Func
public class Calculadora
{
public void Operar(int a, int b, Func<int, int, int> operacion)
{
int resultado = operacion(a, b);
Console.WriteLine($"Resultado: {resultado}");
}
}
// Uso
var calc = new Calculadora();
calc.Operar(5, 3, (x, y) => x + y); // Suma
calc.Operar(5, 3, (x, y) => x * y); // Multiplicación
Aquí estamos delegando la operación matemática a una función anónima que se pasa en tiempo de ejecución. Esto da una flexibilidad enorme en escenarios donde no sabes de antemano qué comportamiento se debe ejecutar. 😃
Desde mi experiencia, estas son algunas de las ventajas más claras:
Personalmente, recomiendo conocer bien cómo funcionan los delegates y el patrón Delegator en C# porque son herramientas muy potentes para escribir código flexible, limpio y mantenible. Ya sea a través de interfaces, delegates o métodos anónimos, delegar responsabilidades te permite construir aplicaciones mucho más robustas.
Espero que este artículo te haya aclarado cómo se usa Delegator en C#, tanto a nivel de patrón como de implementación con delegates. Si quieres, déjame un comentario o escríbeme para seguir profundizando en este tema 📬.
"El mejor código es aquel que es fácil de entender, flexible de mantener y divertido de escribir." ✨
Jorge García
Fullstack developer