Каким типом данных является делегат в C#?

Ответ

Делегат в C# — это безопасный тип указателя на метод, который определяет сигнатуру (возвращаемый тип и параметры). С точки зрения системы типов, делегат является ссылочным типом (class), производным от System.MulticastDelegate.

Суть делегата: Он позволяет инкапсулировать метод в объект, который можно передавать как параметр, хранить в поле или вызывать.

Пример: создание и использование пользовательского делегата

// 1. Объявление типа делегата
public delegate int Processor(int x, int y);

// 2. Методы, совместимые с сигнатурой делегата
static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;

// 3. Создание экземпляра делегата и вызов
Processor operation = Add;
int result = operation(5, 3); // result = 8

// 4. Multicast: делегат может ссылаться на несколько методов
operation += Multiply;
result = operation(5, 3); // Последовательно вызовет Add(5,3), затем Multiply(5,3).
                           // В result будет записан результат последнего метода (15).

Почему делегаты важны? Они — основа событийной модели .NET и LINQ.

Современные встроенные обобщённые делегаты (чаще используются на практике):

  • Action — для методов, которые не возвращают значение (void).
    Action<string> logger = message => Console.WriteLine($"[LOG]: {message}");
    logger("App started");
  • Func — для методов, возвращающих значение.
    Func<int, int, int> calculator = (x, y) => x + y;
    int sum = calculator(10, 20); // sum = 30
  • Predicate<T> — частный случай Func<T, bool>, используется для проверки условий.

Ключевой паттерн на делегатах — Callback:

public void ProcessData(List<int> data, Action<int> progressCallback)
{
    for (int i = 0; i < data.Count; i++)
    {
        // ... обработка ...
        progressCallback?.Invoke(i + 1); // Вызов callback для обновления UI
    }
}

// Использование
ProcessData(myList, progress => Console.WriteLine($"Обработано: {progress} элементов"));

Ответ 18+ 🔞

Давай разберёмся с этими делегатами, а то народ пугается, как чёрт ладана. Представь себе, что делегат — это такой продвинутый пульт дистанционного управления для методов. Не просто кнопка, а целая панель, где ты можешь запрограммировать: "нажми вот эту кнопку — выполнится вот этот метод, который складывает два числа, а нажми другую — перемножь их".

И самое главное — этот пульт ты можешь передать другому, положить в карман, кинуть как параметр в другой метод. Вот в чём, блядь, сила-то!

С точки зрения технической подноготной — это просто ссылочный тип, class, который наследуется от какого-то там System.MulticastDelegate. Но тебе на это похуй, главное — что он типобезопасный. Не как эти твои void* в плюсах, где можно по ошибке вызвать не то и получить ядрёный краш. Тут компилятор тебя придерёт: "Э, дружок-пирожок, метод должен возвращать int и принимать два int, а ты суёшь мне что-то про string — иди нахуй".

Вот смотри, как это выглядит в коде:

// 1. Объявляем тип делегата. Это как чертёж для пульта.
public delegate int Processor(int x, int y);

// 2. Пишем методы, которые подходят под этот чертёж.
static int Add(int a, int b) => a + b;
static int Multiply(int a, int b) => a * b;

// 3. Создаём сам пульт и настраиваем его на метод Add.
Processor operation = Add;
// Жмём кнопку на пульте — вызывается Add.
int result = operation(5, 3); // result = 8

// 4. А вот магия multicast! К одной кнопке привязываем ДВА метода.
operation += Multiply;
// Теперь при вызове сработают оба: сначала Add, потом Multiply.
result = operation(5, 3); // Результат будет от последнего — 15.

А теперь самое вкусное. Microsoft, видя, что народ постоянно объявляет свои делегаты для одних и тех же сигнатур, решила: "Хули тут мозги выносить?". И ввела готовые обобщённые делегаты. Теперь 90% случаев покрываются ими.

  • Action — для методов, которые ничего не возвращают (void). Как будто команда: "Сделай что-то и заткнись".
    Action<string> logger = message => Console.WriteLine($"[LOG]: {message}");
    logger("Приложение стартануло, все в шоке");
  • Func — для методов, которые что-то возвращают. Самый упоротый, потому что последний generic-параметр — это всегда тип возвращаемого значения.
    Func<int, int, int> calculator = (x, y) => x + y;
    int sum = calculator(10, 20); // sum = 30
  • Predicate<T> — это такой заскок прошлого, по сути тот же Func<T, bool>. Нужен, чтобы проверять, подходит объект под условие или нет.

И где это всё, блядь, применяется? Да везде! Основа событий в WinForms/WPF — это делегаты. Весь LINQ — это сплошные делегаты. А ещё есть офигенный паттерн — Callback.

Смотри, как элегантно:

// Ты делаешь долгую работу и хочешь отчитываться о прогрессе.
public void ProcessData(List<int> data, Action<int> progressCallback)
{
    for (int i = 0; i < data.Count; i++)
    {
        // ... тут что-то тяжёлое происходит ...
        // А тут ты дергаешь за ниточку того, кто тебе передал callback.
        progressCallback?.Invoke(i + 1); // "Эй, я обработал ещё один!"
    }
}

// А используешь так. Передаёшь анонимный метод прямо на лету.
ProcessData(myList, progress => Console.WriteLine($"Обработано элементов: {progress}"));

Вот и вся магия. Не боги горшки об delegates, а обычные программисты, которые перестали их бояться. Главное — понять, что это просто типобезопасный способ сказать "вызови вот этот метод вот с такими параметрами", и всё.