Быстрая заметка о принятом ответе: я не согласен с небольшой частью ответа Джеффри, а именно, что, поскольку Delegate
как ссылочный тип, следует, что все делегаты являются ссылочными типами. (Это просто неверно, что многоуровневая цепочка наследования исключает типы значений: все типы перечислений, например, наследуют от 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;
Имеет ли смысл этот вопрос? Как я вижу, есть два возможных объяснения:
- Реализация делегатов должным образом в качестве типов значений потребовала бы дополнительной работы/сложности, поскольку поддержка таких вещей, как блокировки, которые изменяют значения локальных переменных, в любом случае потребовала бы создания ссылочных типов, генерируемых компилятором.
- Есть несколько других причин, почему под капотом делегаты просто не могут быть реализованы как типы значений.
В конце концов, я не теряю сон над этим; это просто то, о чем мне было любопытно немного.
Обновить. В ответ на комментарий Ani я вижу, почему тип CompilerGenerated
в моем примере выше может также быть ссылочным типом, поскольку если делегат будет содержать указатель на функцию и указатель на объект, в любом случае ему понадобится ссылочный тип (по крайней мере, для анонимных функций с использованием закрытий, так как даже если вы представили дополнительный параметр типа generic, например Action<TCaller>
), это не будет охватывать типы, которые нельзя назвать!). Однако, все это делает вид, что я сожалею о том, что вопрос о типах, сгенерированных компилятором, для закрытия в дискуссии вообще! Мой главный вопрос касается делегатов, т.е. Вещи с указателем на функцию и указателем на объект. Мне кажется, что это может быть тип значения.
Другими словами, даже если это...
Action foo = () => { obj.Foo(); };
... требует создания одного объекта ссылочного типа (для поддержки закрытия и предоставления делегату что-то для ссылки), для чего требуется создание two (поддерживающий замыкание объект плюс делегат Action
)?
* Да, да, подробности реализации, я знаю! Все, что я действительно имею в виду, это кратковременная память.