Делегаты и события
Делегат — это тип, значение которого ссылается на метод. Он превращает «вызвать метод» в «передать метод как данные»: сохранить в переменную, положить в список, отдать другому методу параметром. На этой одной идее держатся события, LINQ, коллбэки и весь функциональный стиль в C#.
Ключевое, что отличает делегат от обычного вызова, — компилятор проверяет сигнатуру. Делегат фиксирует набор параметров и тип возврата, и присвоить ему можно только метод с совпадающей сигнатурой; несовпадение отвергается на этапе компиляции, а не во время выполнения. Дальше эта база обрастает слоями: один делегат может хранить целый список целей (multicast), свой тип объявлять не обязательно (Func/Action/Predicate), тело метода можно писать прямо на месте (лямбды и анонимные методы), а лямбда, захватившая внешнюю переменную, становится замыканием. Полная карта — в слоях ниже.
Карта темы
- Делегаты — типобезопасная ссылка на метод, объявление сигнатуры, присваивание и вызов, объект-цель.
- Multicast-делегаты — список вызова,
+=/-=, порядок выполнения, ловушки с возвратом и исключением. - Func, Action, Predicate — встроенные обобщённые делегаты вместо собственных типов.
- Лямбда-выражения — синтаксис
=>, захват переменных, замыкания и их время жизни. - Анонимные методы — старый синтаксис
delegate { }и его связь с лямбдами. - Время привязки — раннее (статическое) и позднее связывание,
dynamicи рефлексия.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Путать делегат с интерфейсом | Делегат ссылается на один метод по сигнатуре, а не описывает контракт целого типа |
| Ждать все возвраты от multicast-делегата | У делегата с возвратом выживает только результат последней цели; ранние отбрасываются |
Считать, что += заменяет цель | += дописывает метод в список вызова, -= удаляет; замены нет |
Объявлять свой тип делегата вместо Func/Action | Лишний шаблонный код там, где встроенный обобщённый делегат уже подходит |
| Думать, что замыкание копирует переменную | Лямбда делит живую переменную по ссылке; изменения видны с обеих сторон |
| Считать позднее связывание быстрее раннего | Наоборот: dynamic/рефлексия теряют проверки компиляции и добавляют накладные расходы |
Значение для собеседований
Делегаты — одна из самых частых тем на собеседовании по C#, потому что за ними тянется вся модель «метод как значение»: события, LINQ, коллбэки, функциональный стиль. Кандидат, который объясняет делегат через «типобезопасная ссылка на метод с проверкой сигнатуры компилятором», сразу отличается от «ну, это указатель на функцию».
Что обычно проверяют:
- Что такое делегат и чем он отличается от интерфейса и от простого вызова метода.
- Как ведёт себя список вызова multicast-делегата: порядок, единственный выживающий возврат, остановка на исключении.
- Когда брать
Func/Action/Predicate, а когда объявлять свой тип. - Что такое замыкание и почему оно делит живую переменную, а не копию.
- Разницу раннего и позднего связывания и цену
dynamic.
Типичный неверный ответ: «multicast-делегат вернёт мне все результаты и выполнит все цели, даже если одна упала». Это открывает разговор о том, что выживает только последний возврат, а исключение обрывает обход списка — и что для сбора всех результатов цели надо обходить вручную через GetInvocationList().