Почему делегаты являются ссылочными типами?

Краткое примечание к принятому ответу : Я не согласен с небольшой частью ответа Джеффри , а именно с тем, что, поскольку Делегат должен быть эталонным типом , из этого следует, что все делегаты являются ссылочными типами. (Это просто неправда, что многоуровневая цепочка наследования исключает типы значений; все типы перечислений, например, наследуются от System.Enum , который, в свою очередь, наследуется от System.ValueType , который наследуется от System.Object , всех ссылочных типов.) Однако я считаю тот факт, что, по сути, все делегаты на самом деле наследуют не только от Delegate , но из MulticastDelegate является здесь критически важной реализацией. Как Раймонд указывает в комментарии к его ответу, если вы взяли на себя обязательство поддерживать несколько подписчиков, на самом деле нет смысла в , а не в , использовать ссылочный тип для сам делегат, учитывая необходимость где-то в массиве.


См. Обновление внизу.

Мне всегда казалось странным, что если я делаю это:

Action foo = obj.Foo;

Я каждый раз создаю новый Action объект. Я уверен, что стоимость минимальная, но она включает выделение памяти для последующего сбора мусора.

Учитывая, что делегаты по своей природе сами неизменны, мне интересно, почему они не могут быть типами значений? Тогда строка кода, подобная приведенной выше, повлечет за собой не что иное, как простое присвоение адреса памяти в стеке *.

Даже с учетом анонимных функций кажется ( мне ) это сработает. Рассмотрим следующий простой пример.

Action foo = () => { obj.Foo(); };

В этом случае foo действительно составляет закрытие , да. И во многих случаях я полагаю, что для этого действительно требуется реальный ссылочный тип (например, когда локальные переменные закрываются и изменяются внутри замыкания). Но в некоторых случаях этого не должно быть. Например, в приведенном выше случае кажется, что тип, поддерживающий замыкание, может выглядеть следующим образом: Я возвращаю свою исходную точку зрения по этому поводу. Приведенный ниже действительно должен быть ссылочным типом (или: он не должен быть , но если это struct , он все равно будет помещен в коробку). Итак, не обращайте внимания на приведенный ниже пример кода. Я оставляю это только для того, чтобы предоставить контекст для ответов, в которых конкретно упоминается об этом.

struct CompilerGenerated
{
    Obj obj;

    public CompilerGenerated(Obj obj)
    {
        this.obj = obj;
    }

    public void CallFoo()
    {
        obj.Foo();
    }
}

// ...elsewhere...

// This would not require any long-term memory allocation
// if Action were a value type, since CompilerGenerated
// is also a value type.
Action foo = new CompilerGenerated(obj).CallFoo;

Имеет ли смысл этот вопрос? На мой взгляд, есть два возможных объяснения:

  • Правильная реализация делегатов как типов значений потребовала бы дополнительной работы / сложности, поскольку для поддержки таких вещей, как замыкания, которые изменяют значения локальных переменных, потребовалось бы в любом случае ссылочные типы, созданные компилятором.
  • Есть несколько других причин, по которым под капотом делегаты просто не могут быть реализованы как типы значений.

В конце концов, я не теряю из-за этого сон; это просто то, что меня любопытно какое-то время.


Обновление : в ответ на комментарий Ани я понимаю, почему тип CompilerGenerated в моем примере выше также может быть ссылочным типом, поскольку если делегат будет содержать указатель на функцию и указатель на объект, ему в любом случае потребуется ссылочный тип (по крайней мере, для анонимных функций, использующих замыкания, поскольку даже если вы ввели дополнительный параметр универсального типа, например Action , это не будет охватывать типы это не может быть названо!). Однако , все это заставляет меня сожалеть о том, что я вообще затронул вопрос о типах, генерируемых компилятором для замыканий! Мой главный вопрос касается делегатов , то есть объекта с указателем функции и указателем объекта. Мне все еще кажется , что может быть типом значения.

Другими словами, даже если этот ...

Action foo = () => { obj.Foo(); };

... требует создания одного объекта ссылочного типа (для поддержки замыкания и предоставления делегату ссылки), почему для этого требуется создание двух (поддерживающий закрытие объект плюс делегат Action )?

* Да, да, детали реализации, я знаю! Все, что я на самом деле имею в виду, это хранилище краткосрочной памяти .

35
задан Community 23 May 2017 в 12:00
поделиться