JavaRush /Курси /C# SELF /Передача лямбда‑виразів ( =&g...

Передача лямбда‑виразів ( =>) як параметр

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 (точніше, нічого явно не повертає). Будьте уважні до типу, який повертається!

Помилка: зловживання лямбдами

Якщо ви починаєте передавати лямбди на десять рядків, краще винесіть їх у окремий метод. Так і читабельніше, і налагоджувати простіше.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ