Делегат в C# — это ссылочный или значимый тип?

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

Ответ

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

Почему это важно и как работает:

  • Ссылочная семантика: При присваивании одной переменной делегата другой копируется ссылка, а не сам объект делегата. Обе переменные начинают указывать на один и тот же экземпляр в куче.
  • Мультикаст-делегаты: Делегаты поддерживают объединение (+, +=), что позволяет создавать цепочки вызовов. Это реализовано через создание нового объекта делегата в куче, который содержит список вызовов (invocation list).
  • Сравнение: При использовании == или .Equals() для делегатов сравниваются не только ссылки, но и целевые объекты и методы, что делает проверку логически осмысленной.

Пример:

// Объявление типа делегата
public delegate void LogMessage(string message);

class Program
{
    static void LogToConsole(string msg) => Console.WriteLine($"[CONSOLE] {msg}");
    static void LogToFile(string msg) => System.IO.File.AppendAllText("log.txt", msg + "n");

    static void Main()
    {
        // Создание экземпляра делегата (ссылочного типа) в куче
        LogMessage logger = LogToConsole;
        logger("App started"); // Вызов через делегат

        // Добавление второго метода. Оператор += создает НОВЫЙ объект делегата в куче.
        logger += LogToFile;
        logger("Data processed"); // Теперь вызовутся оба метода

        // Переменная 'anotherLogger' теперь ссылается на тот же объект, что и 'logger'
        LogMessage anotherLogger = logger;
    }
}

Таким образом, хотя синтаксис работы с делегатами может напоминать работу с методами, технически это полноценные объекты ссылочного типа со своей внутренней структурой.