1. Introducción
Cuando programas, a veces necesitas describir una acción simple que se usa solo una vez y no servirá en ningún otro sitio. Crear un método separado para eso es como clavar un clavo con el martillo de un kit para montar un avión. Mucho más fácil — tomar algo improvisado y resolver la tarea "en el lugar".
Método anónimo — es un fragmento de código temporal que no necesita nombre porque se usa justo donde se crea. Él:
- Se declara dentro de otro método.
- No tiene nombre.
- Normalmente se usa una sola vez — por ejemplo, se pasa a un delegado o se suscribe a un evento.
Hoy en día se usan más las expresiones lambda (el operador =>), pero entender los métodos anónimos ayuda a comprender cómo funcionan los delegados y el modelo de eventos en C#.
¿Cuándo es cómodo usar un método anónimo?
- Necesitas pasar rápidamente un fragmento de lógica como parámetro, por ejemplo, para ordenar, filtrar, manejar eventos, etc.
- No tiene sentido llenar la clase con métodos separados por lógica de un solo uso.
- Cuando quieres que el código sea compacto y fácil de leer: toda la "pequeña lógica de negocio" en un mismo lugar.
Un poco de historia
Por primera vez en C# la posibilidad de crear métodos anónimos apareció en la versión 2.0. Antes de eso usar delegados era engorroso: había que declarar un método nombrado por separado, incluso si su lógica se usaba en un solo sitio. Con la introducción de los métodos anónimos todo se simplificó, y con la llegada de las expresiones lambda esta idea alcanzó un nuevo nivel de concisión.
2. Clásicos: delegados y el método "nombrado" habitual
Antes de conocer los métodos anónimos, recordemos la forma estándar de pasar comportamiento mediante delegados. Supongamos que tenemos una app "Base de libros", donde queremos filtrar libros por distintas condiciones.
// Definimos el delegado
public delegate bool BookFilter(Book book);
// Clase Book
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
Antes escribíamos así:
// Función-filtro separada
public static bool IsClassic(Book b)
{
return b.Year < 1970;
}
// En algún sitio de uso
BookFilter filter = IsClassic;
Crear un método separado cada vez no siempre es cómodo. ¿Y si hay decenas de filtros?
3. Método anónimo: minimalismo, amigo de los delegados
El método anónimo permite definir el filtro justo donde se necesita:
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
¡Eso es todo! Ningún método extra, todo en el mismo sitio. delegate actúa como la declaración de una función sin nombre.
Sintaxis general
delegate([argumentos])
{
// cuerpo del método
};
Ejemplo integrado en un programa:
public class Book
{
public string Title { get; set; }
public int Year { get; set; }
}
public delegate bool BookFilter(Book book);
class Program
{
static void Main()
{
Book[] books = {
new Book { Title = "Master i Margarita", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
// Filtro anónimo para clásicos
BookFilter filter = delegate(Book b)
{
return b.Year < 1970;
};
foreach (Book book in books)
{
if (filter(book)) //¡invocamos la función anónima!
Console.WriteLine($"{book.Title} — ¡clásico!");
}
}
}
Salida:
Master i Margarita — ¡clásico!
4. Método anónimo con distintos delegados
Los métodos anónimos funcionan bien con cualquier delegado. Por ejemplo, los estándar Action y Func<T, TResult>, que analizaremos con más detalle más adelante.
Action<string> sayHello = delegate(string name)
{
Console.WriteLine($"¡Hola, {name}!");
};
sayHello("Mundo"); // ¡Hola, Mundo!
Breve ilustración: cómo funciona un método anónimo "de una sola vez"
Lo más sencillo es imaginar el método anónimo como un trabajador temporal: aparece para una tarea corta, la realiza y desaparece. Vinculamos trabajador y tarea mediante un delegado.
Func<int, int, int> sum = delegate (int a, int b) {
return a + b;
};
Console.WriteLine(sum(5, 7)); // 12
Aplicación: ordenación, búsqueda, procesamiento de colecciones
Supongamos que tenemos una lista de libros que queremos ordenar por año de publicación. Declarar un método separado solo para una comparación de un solo uso es excesivo.
var books = new List<Book>
{
new Book { Title = "Master i Margarita", Year = 1967 },
new Book { Title = "Clean Code", Year = 2008 }
};
books.Sort(delegate(Book a, Book b)
{
return a.Year.CompareTo(b.Year);
});
foreach (var book in books)
Console.WriteLine($"{book.Title} ({book.Year})");
5. Matices útiles
¿Un método anónimo es lo mismo que una expresión lambda?
En C# moderno se usan más las expresiones lambda (=>), y normalmente hay poca diferencia entre ellas, pero hay diferencias conceptuales:
- Las lambdas son más cortas y expresivas.
- Las lambdas tienen reglas de captura de variables más claras (aprenderemos esto más adelante).
- Los métodos anónimos aparecieron antes y a veces se encuentran en código legado.
Así se vería nuestro ejemplo con una lambda:
BookFilter filter = b => b.Year < 1970;
Sin embargo, saber cómo funciona tanto la sintaxis vieja como la nueva es útil para entrevistas, leer código ajeno y entender a fondo los delegados.
Métodos anónimos con parámetros y sin parámetros
Si el delegado no recibe nada, se puede escribir en una sola línea:
Action printHello = delegate { Console.WriteLine("¡Hola!"); };
printHello(); // ¡Hola!
Si acepta parámetros — igualmente se puede poner en una sola línea:
Action<int> printSquare = delegate (int x) { Console.WriteLine(x * x); };
printSquare(6); // 36
Método anónimo y expresión lambda
| Método anónimo | Expresión lambda | |
|---|---|---|
| Sintaxis | |
|
| Captura de variables | Sí | Sí |
| Popularidad | Se usa raramente | Estándar |
| Retorno de valor | Puede/obligatorio | Puede/obligatorio |
| Multilínea | Se puede | Se puede |
6. Métodos anónimos — detalles y matices de uso
Captura de variables locales
Los métodos anónimos pueden usar variables del método que los rodea — este mecanismo se llama "closure" (cierre). Por ejemplo:
int minYear = 1970;
BookFilter filter = delegate(Book book)
{
return book.Year < minYear;
};
Console.WriteLine(filter(new Book { Title = "Test", Year = 1960 })); // True
Si más tarde cambias minYear, ¡el filtro usará el nuevo valor!
Puedes no especificar parámetros
Si no necesitas parámetros:
Action sayHi = delegate { Console.WriteLine("¡Hi!"); };
Pasar null
Si no asignas un método al delegado (por ejemplo, un anónimo), su valor es null, y el intento de invocarlo causará una NullReferenceException. Ten cuidado.
Multilínea
Un método anónimo puede contener un bloque de código completo, condiciones, bucles e incluso otras llamadas:
Action manyThings = delegate
{
Console.WriteLine("¡Empezamos!");
for (int i = 0; i < 3; i++)
Console.WriteLine(i);
Console.WriteLine("¡Terminamos!");
};
manyThings();
7. Errores típicos y particularidades
A veces los programadores confunden el ámbito de las variables: capturar valores desde un bucle o un método anidado puede producir resultados inesperados.
List<Action> actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
actions.Add(delegate { Console.WriteLine(i); });
}
foreach (var action in actions) action(); // 3 3 3 — ¡sorpresa!
La cuestión es que el método anónimo "ve" la variable i — cuando el bucle termina, i vale 3. Todos los métodos imprimen lo mismo. Para el comportamiento correcto es mejor capturar la variable así:
for (int i = 0; i < 3; i++)
{
int current = i;
actions.Add(delegate { Console.WriteLine(current); });
}
GO TO FULL VERSION