JavaRush /Курсы /C# SELF /Передача лямбда-выражений ( =...

Передача лямбда-выражений ( =>) как параметр

C# SELF
50 уровень , 2 лекция
Открыта

1. Введение

До сих пор вы видели: мы вызывали методы LINQ, которые принимали на вход лямбду. Но как метод C# узнаёт, что ему вообще можно передать функцию?

Простыми словами, делегаты — это как интерфейс для функций: мы описываем подпись (типы параметров и возвращаемое значение), и любой метод (и лямбда!), подходящий по этой подписи, может быть передан туда, где ожидается такой делегат.

Вспомните, как вы передавали строку как параметр? Вот примерно так же работает и с “логикой”, только тип параметра — делегат.

Делегаты: базовая теория с человеческим лицом

В C# делегат — это тип, который описывает “функцию с такой-то сигнатурой”.

// Делегат, который принимает int и возвращает bool
public delegate bool IntPredicate(int x);

Любая функция, совместимая по сигнатуре, может быть присвоена переменной этого типа:

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

IntPredicate pred = IsEven;

А теперь — лямбда подходит:

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

Универсальные делегаты: Func, Action, Predicate

  • Func<T1, ..., TResult> — функция, принимающая параметры T1, ... и возвращающая TResult.
  • Action<T1, ...> — функция, принимающая параметры и не возвращающая значения (void).
  • Predicate<T> — функция, принимающая T и возвращающая bool.

2. Передача лямбды в свой метод

Представим, что мы развиваем наше учебное мини-приложение — консольный проект, который работает со списком пользователей. Раньше мы фильтровали коллекции через LINQ, а теперь напишем свой метод, который принимает лямбду-условие.

Создаём свой метод с лямбдой-параметром

// Определим класс User для примера (добавим в наше приложение)
public class User
{
    public string Name { get; set; }
    public bool IsActive { get; set; }
}

// Метод, который принимает список и делегат-условие (лямбду)
public static List<User> FilterUsers(List<User> users, Predicate<User> predicate)
{
    var result = new List<User>();
    foreach (var user in users)
    {
        if (predicate(user)) // Вызываем лямбду!
            result.Add(user);
    }
    return result;
}

Теперь можно передавать любую лямбду:

var users = new List<User>
{
    new User { Name = "Вася", IsActive = true },
    new User { Name = "Петя", IsActive = false },
    new User { Name = "Маша", IsActive = true }
};

// Фильтруем только активных
var activeUsers = FilterUsers(users, user => user.IsActive);

foreach (var user in activeUsers)
    Console.WriteLine(user.Name); // Вася, Маша

Вот и всё! Мы передали кусочек логики — мини-функцию — как обычный параметр, просто потому что метод FilterUsers ждёт Predicate<User>, а мы дали ему подходящую лямбду.

Вариант с Func<T, TResult>

Predicate<T> подходит, когда нужно условие (возврат bool). А если хотим что-то “вычислить” для каждого пользователя?

// Метод, который применяет функцию к каждому элементу и собирает результаты
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;
}

Использование:

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

foreach (var name in names)
    Console.WriteLine(name); // ВАСЯ, ПЕТЯ, МАША

3. Полезные нюансы

Разные формы передачи

Можно передать не только лямбду, но и обычный метод — главное, чтобы сигнатура совпадала.

// Обычный метод
static bool NameHasS(User user) => user.Name.Contains("с");

// Передача обычного метода:
var usersWithS = FilterUsers(users, NameHasS);
// Передача лямбды
var usersWithA = FilterUsers(users, u => u.Name.Contains("а"));

А можно и анонимный метод старого образца (но не надо так):

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

Современный стиль — лямбды!

Передача лямбд в LINQ: что реально происходит

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

Под капотом Where принимает Func<User, bool>. Это значит, что любой метод, принимающий Func<...>, может быть использован так же!

А что, если хочется два параметра?

// Метод принимает две лямбды для фильтрации
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;
}

Использование:

var customFiltered = FilterUsersCustom(
    users,
    u => u.Name.StartsWith("В"),
    u => u.IsActive == false
);
// Возьмёт только пользователей, чьи имена начинаются на "В" и кто активен

Сценарий: Фабрика фильтров

Console.WriteLine("Введите минимальную длину имени:");
int minLength = int.Parse(Console.ReadLine());

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

// Весьма интерактивно и живенько!

4. Типичные ошибки и нюансы

Иногда компилятор не может “вывести” тип параметров лямбды — особенно в сложных сценариях с перегрузками или когда метод требует делегата с несколькими параметрами/конкретным возвращаемым типом. В таком случае можно явно указать типы лямбды:

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

или даже:

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

Ошибка: лямбда не подходит по сигнатуре

FilterUsers(users, user => Console.WriteLine(user.Name)); // ошибка!  Ожидается bool, получен void

Потому что ожидается функция, возвращающая bool, а лямбда возвращает void (точнее, ничего явно не возвращает). Будьте внимательны к возвращаемому типу!

Ошибка: злоупотребление лямбдами

Если вы начинаете передавать лямбды из 10 строк, лучше вынесите их в отдельный метод. Это и читабельнее, и отладка проще.

2
Задача
C# SELF, 50 уровень, 2 лекция
Недоступна
Сложная фильтрация и преобразование пользователей
Сложная фильтрация и преобразование пользователей
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ