Что такое делегат (Delegate) в C#?

«Что такое делегат (Delegate) в C#?» — вопрос из категории C# Core, который задают на 65% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Делегат (Delegate) в C# — это типобезопасный указатель на метод. По сути, это объект, который знает, как вызвать конкретный метод (или группу методов), позволяя передавать методы как параметры, хранить их в переменных и вызывать динамически.

Зачем это нужно? Для реализации паттернов обратного вызова (callback), событийной модели (events) и для повышения гибкости кода, позволяя менять поведение алгоритма, не переписывая его.

Ключевые особенности:

  1. Типобезопасность: Сигнатура делегата (возвращаемый тип и типы параметров) должна точно совпадать с сигнатурой метода, на который он ссылается.
  2. Multicast-делегаты: Один делегат может содержать ссылки на несколько методов (+= для добавления, -= для удаления). При вызове такого делегата методы выполняются последовательно.

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

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

public class Calculator
{
    // 2. Методы, совместимые с делегатом
    public static int Add(int x, int y) => x + y;
    public static int Multiply(int x, int y) => x * y;
}

class Program
{
    static void Main()
    {
        // 3. Создание экземпляра делегата и привязка к методу
        MathOperation operation = Calculator.Add;

        // 4. Вызов метода через делегат
        int result = operation(5, 3); // result = 8
        Console.WriteLine(result);

        // 5. Добавление второго метода (Multicast)
        operation += Calculator.Multiply;
        // При вызове выполнятся оба метода: Add(5,3) и Multiply(5,3)
        // Но возвращено будет значение только от последнего (Multiply -> 15)
        result = operation(5, 3); // result = 15
        Console.WriteLine(result);
    }
}

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

  • Action<T1, T2, ...> — для методов, которые не возвращают значение (void).
    Action<string> logAction = message => Console.WriteLine($"[LOG]: {message}");
    logAction("Hello");
  • Func<T1, T2, ..., TResult> — для методов, которые возвращают значение.
    Func<int, int, int> sumFunc = (a, b) => a + b;
    int sum = sumFunc(10, 20); // sum = 30
  • Predicate<T> — для методов, возвращающих bool (частный случай Func<T, bool>).

Основные сферы применения:

  • События (Events): Основа механизма событий в .NET.
  • LINQ: Многие методы расширения LINQ принимают Func<> или Action<> как параметры.
  • Асинхронное программирование: В связке с async/await.
  • Реализация паттернов: Например, Стратегия (Strategy), где делегат инкапсулирует алгоритм.