CodeGym /Các khóa học /C# SELF /Delegate như một kiểu cho biểu thức lambda

Delegate như một kiểu cho biểu thức lambda

C# SELF
Mức độ , Bài học
Có sẵn

1. Введение

Hãy tưởng tượng bạn có một máy pha cà phê. Thường thì nó chỉ pha cà phê và không làm phiền ai, nhưng đôi khi sếp nói: "Bạn có thể, làm ơn, sau khi pha xong hét 'Xong!'" — và ở đây cần cấu hình. Chúng ta không sửa máy pha. Chỉ cần tạo một hàm mà nó sẽ gọi ở cuối.

Trong C# vai trò này được thực hiện bởi các delegate — chúng cho phép truyền vào phương thức những đoạn mã (hàm, lambda hoặc anonymous method), để phương thức đó gọi chúng khi cần. Nói gọn, delegate là một kiểu có thể giữ tham chiếu tới các phương thức có chữ ký xác định.

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

Trong C# delegate được định nghĩa bằng từ khóa delegate. Ví dụ:

// Delegate tham chiếu tới các phương thức nhận int và trả về bool
public delegate bool PredicateInt(int x);

Bây giờ biến kiểu PredicateInt có thể tham chiếu tới bất kỳ phương thức (hoặc lambda!) nào nhận một int và trả về bool.

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

  • Truyền logic như một tham số (ví dụ, cho sorting, filtering, xử lý sự kiện)
  • Đăng ký sự kiện (sẽ nói sau)
  • Triển khai callback
  • API linh hoạt, nơi phần hành vi được định nghĩa bởi bên gọi

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

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

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

Синтаксис

Khi bạn viết một biểu thức lambda, ví dụ x => x > 5, về cơ bản bạn đang tạo một đối tượng delegate. Lambda không "sống" riêng lẻ: nó cần một kiểu (ai đó phải biết tập tham số và kiểu trả về). Đó là lý do biểu thức lambda trong C# luôn được chuyển (ngầm hoặc rõ ràng) thành một delegate.

Пример 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<...>

Dùng cho các phương thức nhận tham số và trả về giá trị.

Сигнатура:
— Kiểu cuối cùng là kiểu trả về, các kiểu trước đó là kiểu các tham số, ví dụ:
Func<int, string> — nhận int, trả về string

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

Action<...>

Dùng khi cần thực thi một việc nào đó nhưng không trả về gì (void).

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

Predicate<T>

Về cơ bản là viết tắt của Func<T, bool>. Dùng khi cần kiểm tra logic trên một đối tượng (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#        -->     Объект delegate (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).

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION