JavaRush /Курсы /C# SELF /Делегаты как тип для лямбда-выражений

Делегаты как тип для лямбда-выражений

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

1. Введение

Представьте, что у вас есть кофемашина. Обычно она просто варит кофе и никого не трогает, но иногда шеф говорит: «А можешь, пожалуйста, после варки кофе покричать “Готово!”?» — и здесь нужна настройка. Саму кофемашину мы не переписываем. Просто создаём функцию, которую она выполнит в конце.

В C# эту роль выполняют делегаты — они позволяют передавать в методы куски кода (методы, лямбды или анонимные методы), чтобы тот метод вызвал их в нужный момент. Грубо говоря, делегат — это тип, который может хранить ссылки на методы с определённой сигнатурой.

Определение делегата

В C# делегат определяется с помощью ключевого слова delegate. Пример:

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

Теперь переменная типа PredicateInt сможет ссылаться на любой метод (или лямбду!), который принимает один int и возвращает bool.

Для чего нужны делегаты?

  • Передача логики как аргумента (например, для сортировки, фильтрации, обработки событий)
  • Подписка на события (о них мы поговорим позже)
  • Реализация обратных вызовов (callback)
  • Гибкие API, где часть поведения определяется вызывающей стороной

Простая визуальная схема

Тип делегата Сигнатура Пример вызова
Action
void()
Action act = () => ...
Func<int>
int()
Func<int> f = () => 42
Func<int, bool>
bool(int)
Func<int, bool> p = x => x > 0

2. Как лямбда превращается в делегат

Синтаксис

Когда вы пишете лямбда-выражение, например, x => x > 5, вы, по сути, создаёте объект делегата. Лямбда не «живет» в вакууме: ей обязательно нужен тип (кто-то должен знать, какой у неё набор параметров и возвращаемый результат). Вот почему лямбда-выражение в C# всегда неявно (или явно) преобразуется в делегат.

Пример 1: Связывание делегата с методом

// Явно определяем делегат
public delegate bool MyPredicate(int number);

class Program
{
    static void Main()
    {
        // Присваиваем лямбду переменной типа MyPredicate
        MyPredicate isEven = x => x % 2 == 0;

        Console.WriteLine(isEven(4));  // true
        Console.WriteLine(isEven(7));  // false
    }
}

Пример 2: Использование стандартных делегатов

C# содержит набор стандартных обобщённых делегатов: Action, Func<>, Predicate<>. Они используются почти везде, где вы пишете лямбды в коде.

// Используем Func
  
    Func
   
     isPositive = number => number > 0; Console.WriteLine(isPositive(-5)); // false 
   
  

3. Стандартные делегаты: Func, Action, Predicate

Func<...>

Используется для методов, которые что-то принимают и что-то возвращают.

Сигнатура:
— Последний тип — возвращаемое значение, остальные до него — типы параметров, например:
Func<int, string> — принимает int, возвращает string

Func
  
    intToString = number => "Число: " + number; Console.WriteLine(intToString(7)); // "Число: 7" 
  

Action<...>

Используется, если надо что-то выполнить, но возвращать ничего не надо (void).

Action
  
    printHello = name => Console.WriteLine("Привет, " + name + "!"); printHello("Василий"); // "Привет, Василий!" 
  

Predicate<T>

По сути, сокращение для Func<T, bool>. Используется, когда требуется логическая проверка над объектом (true/false).

Predicate
  
    isOdd = x => x % 2 != 0; Console.WriteLine(isOdd(3)); // true 
  

Визуальная шпаргалка

Делегат Сигнатура Применение
Func<T1, TResult>
Result(T1)
Преобразование, проекция
Action<T1>
void(T1)
Сайд-эффекты, вывод
Predicate<T>
bool(T)
Фильтрация, поиск

Какой тип делегата выбрать для лямбда-выражения?

  • Если ожидается возвращаемое значение, возьмите Func<...>
  • Если метод ничего не возвращает (void), используйте Action<...>
  • Если нужна проверка условия, используйте Predicate<T>

Пример: Фильтрация списка

List
  
    numbers = new List
   
     { 1, 2, 3, 4, 5, 6 }; // Ожидает Predicate
    
      List
     
       evenNumbers = numbers.FindAll(x => x % 2 == 0); Console.WriteLine(string.Join(", ", evenNumbers)); // 2, 4, 6 
     
    
   
  

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

Лямбда-выражения и методы коллекций: что под капотом?

Когда вы вызываете метод коллекции, передавая лямбду, например:

var adults = users.Where(u => u.Age >= 18);

Метод Where ожидает аргумент типа Func<T, bool>. То есть, ваша лямбда u => u.Age >= 18 превращается компилятором в объект делегата такого типа.

Блок-схема: как это работает


Ваша лямбда         -->      Компилятор C#        -->     Объект делегата (Func
  
   ) (u => u.Age >= 18) [тип известен] (Готов к вызову в Where()) 
  

Детальнее о типизации: вывод типа

Обычно тип делегата выводится компилятором автоматически, если он ясен из контекста. Например, для метода List<T>.Find ожидается Predicate<T>, и компилятор знает тип параметра по сигнатуре метода.

List
  
    words = new List
   
     { "one", "two", "three" }; var result = words.Find(word => word.Length == 5); // Find ожидает Predicate
    
      Console.WriteLine(result); // "three" 
    
   
  

Если же контекст не ясен, придётся помочь компилятору:

// Явно указываем тип
Func
  
    check = x => x > 10; 
  

Возвращаемые делегаты: фабрика функций

Иногда методы могут возвращать делегаты — то есть создавать «фабрики функций». Это удобно для генерации динамического поведения.

// Функция, возвращающая делегат (лямбду)
Func
  
    GetMultiplier(int factor) { return x => x * factor; } var times3 = GetMultiplier(3); Console.WriteLine(times3(5)); // 15 
  

Это работает, потому что лямбда (x => x * factor) захватывает переменную factor из внешнего контекста (closure/замыкание) и возвращается как объект типа Func<int, int>.

5. Ошибки и недоразумения с делегатами и лямбдами

Несовпадение сигнатур

Компилятор не даст записать лямбду в делегат, если параметры или возвращаемое значение не совпадают.

Func
  
    f = x => "Нельзя вернуть строку!"; // Ошибка компиляции 
  

Ошибка при попытке использовать лямбду без делегата

Нельзя просто написать лямбду и попробовать её вызвать без типа:

// Это не сработает - компилятор не может вывести тип
// var myFunc = x => x * 2; // Ошибка CS0815 
// myFunc(10);

Чтобы работало, нужно явно указать тип, или предоставить контекст:

Func
  
    myFunc = x => x * 2; Console.WriteLine(myFunc(10)); // 20 
  

Путаница между Action, Func и Predicate

Иногда можно случайно выбрать не тот тип делегата, из-за чего будет ошибка соответствия сигнатуры. Помните простое правило: Func — когда есть результат, Action — когда результата нет (void), Predicate<T> — когда нужен логический ответ (bool).

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