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