CodeGym /Cursos /C# SELF /Pasar expresiones lambda ( =&...

Pasar expresiones lambda ( =>) como parámetro

C# SELF
Nivel 50 , Lección 2
Disponible

1. Introducción

Hasta ahora viste que llamábamos métodos de LINQ que aceptaban una lambda como entrada. Pero, ¿cómo sabe un método de C# que allí se le puede pasar una función?

En pocas palabras, los delegados son como una interfaz para funciones: describimos la firma (tipos de parámetros y valor de retorno), y cualquier método (¡y lambda!) que coincida con esa firma puede ser pasado donde se espera ese delegado.

¿Recuerdas cómo pasabas una cadena como parámetro? Pues con la “lógica” es casi lo mismo, solo que el tipo del parámetro es un delegado.

Delegados: teoría básica en cristiano

En C# un delegado es un tipo que describe “una función con tal firma”.

// Delegado que acepta int y devuelve bool
public delegate bool IntPredicate(int x);

Cualquier función compatible por firma puede asignarse a una variable de ese tipo:

bool IsEven(int n) => n % 2 == 0;

IntPredicate pred = IsEven;

Y ahora — una lambda encaja:

IntPredicate pred = x => x % 2 == 0;

Delegados genéricos: Func, Action, Predicate

  • Func<T1, ..., TResult> — función que acepta parámetros T1, ... y devuelve TResult.
  • Action<T1, ...> — función que acepta parámetros y no devuelve valor (void).
  • Predicate<T> — función que acepta T y devuelve bool.

2. Pasar una lambda a tu método

Imagina que estamos desarrollando nuestra mini-aplicación de aprendizaje — un proyecto de consola que trabaja con una lista de usuarios. Antes filtrábamos colecciones con LINQ, ahora escribiremos nuestro propio método que acepta una lambda-condición.

Creamos nuestro método con un parámetro lambda

// Definimos la clase User como ejemplo (añádela a la aplicación)
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

// Método que acepta una lista y un delegado-condición (lambda)
public static List<User> FilterUsers(List<User> users, Predicate<User> predicate)
{
    var result = new List<User>();
    foreach (var user in users)
    {
        if (predicate(user)) // ¡Invocamos la lambda!
            result.Add(user);
    }
    return result;
}

Ahora se puede pasar cualquier lambda:

var users = new List<User>
{
    new User { Name = "Vasya", IsActive = true },
    new User { Name = "Petya", IsActive = false },
    new User { Name = "Masha", IsActive = true }
};

// Filtramos solo los activos
var activeUsers = FilterUsers(users, user => user.IsActive);

foreach (var user in activeUsers)
    Console.WriteLine(user.Name); // Vasya, Masha

¡Y ya está! Pasamos un pedazo de lógica — una mini-función — como un parámetro normal, simplemente porque el método FilterUsers espera un Predicate<User>, y le dimos una lambda compatible.

Variante con Func<T, TResult>

Predicate<T> encaja cuando necesitas una condición (retorno bool). ¿Y si queremos “calcular” algo para cada usuario?

// Método que aplica una función a cada elemento y recoge los resultados
public static List<TResult> MapUsers<TResult>(List<User> users, Func<User, TResult> selector)
{
    var result = new List<TResult>();
    foreach (var user in users)
    {
        result.Add(selector(user));
    }
    return result;
}

Uso:

var names = MapUsers(users, user => user.Name.ToUpper());

foreach (var name in names)
    Console.WriteLine(name); // VASYA, PETYA, MASHA

3. Matices útiles

Diferentes formas de pasar

Puedes pasar no solo una lambda, sino también un método normal — lo importante es que la firma coincida.

// Método normal
static bool NameHasS(User user) => user.Name.Contains("s");

// Pasando el método normal:
var usersWithS = FilterUsers(users, NameHasS);
// Pasando una lambda
var usersWithA = FilterUsers(users, u => u.Name.Contains("a"));

También puedes usar un método anónimo a la manera antigua (pero no lo recomiendo):

var usersWithM = FilterUsers(users, delegate(User u) { return u.Name.Contains("m"); });

El estilo moderno: ¡lambdas!

Pasar lambdas a LINQ: qué pasa en realidad

var result = users.Where(u => u.IsActive).ToList();

Por debajo, Where acepta Func<User, bool>. Eso significa que cualquier método que acepte Func<...> puede usarse igual.

¿Y si quieres dos parámetros?

// Método que acepta dos lambdas para filtrar
public static List<User> FilterUsersCustom(
    List<User> users,
    Func<User, bool> include,
    Func<User, bool> exclude)
{
    var result = new List<User>();
    foreach (var user in users)
    {
        if (include(user) && !exclude(user))
            result.Add(user);
    }
    return result;
}

Uso:

var customFiltered = FilterUsersCustom(
    users,
    u => u.Name.StartsWith("V"),
    u => u.IsActive == false
);
// Tomará solo usuarios cuyo nombre empieza por "V" y que estén activos

Escenario: Fábrica de filtros

Console.WriteLine("Introduce la longitud mínima del nombre:");
int minLength = int.Parse(Console.ReadLine());

Predicate<User> lengthFilter = user => user.Name.Length >= minLength;
var filteredUsers = FilterUsers(users, lengthFilter);

// Bastante interactivo y vivo!

4. Errores típicos y matices

A veces el compilador no puede "inferir" el tipo de los parámetros de la lambda — especialmente en escenarios complejos con sobrecargas o cuando el método requiere un delegado con varios parámetros/retorno concreto. En ese caso puedes indicar explícitamente los tipos de la lambda:

FilterUsers(users, (User u) => u.Name.Length > 3);

o incluso:

MapUsers(users, (User u) => u.Name.ToUpper());

Error: la lambda no coincide con la firma

FilterUsers(users, user => Console.WriteLine(user.Name)); // ¡error! Se esperaba bool, se obtuvo void

Porque se espera una función que devuelva bool, y la lambda devuelve void (más precisamente, no devuelve nada). Presta atención al tipo de retorno.

Error: abuso de lambdas

Si empiezas a pasar lambdas de 10 líneas, mejor sácalas a un método separado. Así es más legible y depurarlo es más sencillo.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION