CodeGym /Cursos /C# SELF /Expresiones lambda con Fun...

Expresiones lambda con Func, Action, Predicate

C# SELF
Nivel 50 , Lección 1
Disponible

1. Introducción

¿Sabes qué es curioso? La expresión lambda en sí misma es como una receta sin olla. Describe qué hacer, pero necesita un "contenedor" para vivir en el código. En C# hay tipos genéricos de delegados listos para esto: Func, Action y Predicate.

Imagínatelos como moldes listos para tus lambdas: eliges el adecuado y viertes tu lógica. Nada de reinventar la rueda ni definir delegados personalizados cuando ya tienes todo lo necesario.

Un poco de historia

Al principio, cuando hacía falta pasar una función como parámetro, había que declarar un tipo de delegado propio. Era largo, tedioso y se veía así:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

Cuando en C# aparecieron Func, Action y Predicate, pudiste olvidarte de declarar delegados a mano en el 90% de los casos. Ahora se ve mucho más sencillo y genérico:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — función con retorno

Sintaxis y propósito

Func es un delegado genérico que acepta de 0 a 16 parámetros y devuelve un valor.

Func<int, int, int> sum = (x, y) => x + y;

Func<tipo1, tipo2, ..., tipoN, TResult> — todos los parámetros hasta el último son los argumentos, el último es el tipo devuelto.

Ejemplos

1. Suma de dos números

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. Elevar un número al cuadrado

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. Sin parámetros

Func<string> greet = () => "Hello, lambda!";
Console.WriteLine(greet());

Esquema visual

Firma Ejemplo Descripción
Func<int, int>
x => x * 2
Acepta int, devuelve int
Func<int, int, int>
(a, b) => a + b
Dos int, devuelve int
Func<string>
() => "hi"
No acepta nada, devuelve una cadena

Cómo se ve en tu aplicación

Supongamos que en nuestra mini-aplicación (el hipotético "Directorio de usuarios") tenemos una lista de números — queremos aplicarles diferentes transformaciones. Por ejemplo, elevar al cuadrado o sumar con un número fijo:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — acción sin retorno

Action es un delegado genérico para métodos que hacen algo (por ejemplo, imprimen en pantalla) pero no devuelven nada.

Puede aceptar de 0 a 16 parámetros, pero siempre devuelve void.

Ejemplos

1. Impriworld en pantalla

Action<string> print = text => Console.WriteLine("Data: " + text);
print("Hello, world!");

2. Acción sin parámetros

Action greet = () => Console.WriteLine("Welcome!");
greet();

3. Acción con varios parámetros

Action<int, int> showSum = (a, b) => Console.WriteLine($"Sum: {a + b}");
showSum(2, 3); // Sum: 5

Esquema visual

Firma Ejemplo Descripción
Action
() => ...
Sin parámetros, sin retorno
Action<int>
x => ...
Un parámetro
Action<int, string>
(x, s) => ...
Varios parámetros

En nuestra aplicación

Añadamos al directorio de usuarios un método para impriworld todos los nombres:

List<string> names = new() { "Anna", "Boris", "Vika" };
Action<string> printName = name => Console.WriteLine("User: " + name);

names.ForEach(printName);
// o así: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — ¿sí o no?

Cuando necesitas una función que devuelva solo true o false para un parámetro, usa Predicate<T>. Es un delegado que acepta un parámetro y devuelve bool.

Predicate es la envoltura oficial de "necesitamos una comprobación booleana" para lambdas.

Ejemplos

1. Comprobar si un número es mayor que 5

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. Uso con el método List<T>.Find

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // usa Predicate<int>
Console.WriteLine(found); // 7

3. ¿Todos son adultos?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll acepta Predicate<int>

¿En qué se diferencia de Func<T, bool>?

En la práctica, son totalmente intercambiables. Incluso la documentación de Microsoft dice: "Predicate<T> es simplemente Func<T, bool> para APIs especiales". Pero algunos métodos de la librería estándar piden específicamente Predicate.

5. Cómo las lambdas "encajan" en Func, Action, Predicate

Cuando escribes una lambda, C# analiza: "¡Ah, su forma coincide con el delegado requerido — se puede asignar!"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

Lambdas por todas partes. Pero bajo el capó son tres delegados distintos con firmas diferentes.

Aplicación con código "real"

List<User> users = new() {
    new User("Anna", 24),
    new User("Boris", 17),
    new User("Vika", 31),
};

// Función que devuelve solo usuarios adultos (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("List of adults: " + string.Join(", ", adults.Select(u => u.Name)));

Y si queremos impriworld los nombres de todos los usuarios con Action<User>:

users.ForEach(user => Console.WriteLine(user.Name));

Obtener sus nombres (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

Tabla para mayor claridad

Delegado Firma Ejemplo de lambda Dónde se usa
Func<T, U>
T → U
user => user.Name
Select, cualquier transformación
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, métodos-acción
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, filtros

6. Ejemplos con la aplicación interna: paso a paso

Vamos a ampliar nuestra pequeña aplicación "Directorio de usuarios". Supongamos que tenemos la clase User:

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — comprobamos si el usuario es mayor de edad

Func<User, bool> isAdult = user => user.Age >= 18;

Usamos en LINQ:

var adults = users.Where(isAdult);

2. Predicate<User> — buscamos un usuario activo

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — desactivamos a un usuario

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — obtenemos una descripción corta

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

Todas estas lambdas son código en forma de datos: se pueden pasar a métodos, guardar en variables y combinar.

7. Matices no obvios y errores típicos

1. La lambda debe coincidir con la firma del delegado. Si la firma no coincide, habrá error de compilación.

Func<int, string> wrong = x => x * 2; // error: se esperaba string, se obtuvo int
// Correcto:
Func<int, string> right = x => (x * 2).ToString();

2. No olvides void vs return. Action no devuelve valor — intentar algo como Action<int> a = x => x * x; no funcionará, porque devuelve un valor cuando no debería.

3. Predicate<T> y Func<T, bool> a menudo son intercambiables, pero no siempre. A veces los métodos de colección esperan exactamente Predicate<T>, otras veces Func<T, bool>. La asignación directa no funciona sin un envoltorio explícito.

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // error
// Pero:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // se puede convertir vía constructor
2
Tarea
C# SELF, nivel 50, lección 1
Bloqueada
Uso de `Action` para procesar una lista de cadenas
Uso de `Action` para procesar una lista de cadenas
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION