Какова внутренняя реализация делегата в .NET?

Я понимаю, что объявление делегата выглядит примерно так:

public delegate int PerformCalculation(int x, int y);

Тем не менее, должно быть больше. Цель делегата — предоставить указатель на метод, и для этого вы инкапсулируете ссылку на метод в делегате.

В какой структуре хранится эта ссылка (внутри делегата)? Я также понимаю, что вы можете инкапсулировать ссылку на несколько методов в делегате. Означает ли это, что в делегате есть массив, который их содержит?

Кроме того, какие методы определены в делегате и т. д. Что на самом деле происходит, когда вы объявляете делегат с помощью краткого кода:

public delegate int PerformCalculation(int x, int y);

?

EDIT: Некоторое уточнение. Когда вы объявляете делегата, компилятор автоматически создает для вас запечатанный класс, который наследуется от System.MulticastDelegate. Вы можете увидеть это, если посмотрите на свою сборку с помощью ildasm. Это аккуратно. По сути, с помощью одного оператора вы получаете совершенно новый класс, созданный для вас во время компиляции, и он имеет все необходимые вам функции.


person richard    schedule 09.01.2011    source источник
comment
Вы можете проверить многое с помощью отражателя. Некоторые части отсутствуют, потому что они требуют магии времени выполнения. Но тем не менее вы увидите большую часть содержимого одиночных и многоадресных делегатов.   -  person CodesInChaos    schedule 10.01.2011


Ответы (2)


Все делегаты наследуются от типа System.Delegate, который содержит Цель и Метод. Точнее, они наследуются от System.MultiCastDelegate, который наследуется от System.Delegate.

person Darin Dimitrov    schedule 09.01.2011
comment
Спасибо! Это именно то, что я искал. Я чувствую себя глупо, потому что не нашел это сам. Кажется, я не могу найти, как делегат содержит несколько ссылок. У него есть цель и метод, но они не похожи на collections. . . Что мне здесь не хватает? - person richard; 10.01.2011
comment
@Richard DesLonde, вам не хватает System.MulticastDelegate, который от чего наследуются все делегаты. Я обновил свой ответ для большей ясности. - person Darin Dimitrov; 10.01.2011
comment
Они не реализуют - они наследуют. - person Ani; 10.01.2011
comment
Спасибо, так что внутренне делегат многоадресной рассылки имеет связанный список делегатов? @Ani: Спасибо за исправления. Это имеет смысл. - person richard; 10.01.2011
comment
@richard Возможно, они использовали связанный список в первой версии фреймворка, но в текущих версиях используется простой неизменяемый массив делегатов, который выбрасывается каждый раз, когда вы добавляете или удаляете делегаты в многоадресном делегате. - person Ark-kun; 30.07.2014

Внутри это ссылочный тип, очень похожий на класс. В расшифровке это выглядит так:

public /* delegate */ class PerformCalculation : MulticastDelegate {
    public PerformCalculation(object target, IntPtr method) {}
    public virtual void Invoke(int x, int y) {}
    public virtual IAsyncResult BeginInvoke(int x, int y, AsyncCallback callback, object state) {}
    public virtual void EndInvoke(IAsyncResult result) {}
}

Я оставил реализации этих элементов пустыми, они фактически отображаются в коде в CLR. Компилятор динамически генерирует сигнатуры методов в зависимости от сигнатуры объявления делегата. Обратите внимание на аргументы x и y. Компилятор JIT помогает вызвать конструктор, используя синтаксис += или -=, он знает адрес памяти целевого метода делегата. Компилятор автоматически генерирует значение аргумента target в зависимости от того, является ли целевой метод статическим или нет. Два аргумента сопоставляются со свойствами (Multicast)Delegate.Target и Method. Фактическим экземпляром базового класса может быть либо Delegate, либо MulticastDelegate, в зависимости от того, сколько целей было подписано.

Здесь происходит много секретного соуса.

person Hans Passant    schedule 09.01.2011
comment
Хорошая вещь. Можете ли вы объяснить накладные расходы, связанные с вызовом делегата? В частности, по сравнению с вызовом виртуального метода. - person Ani; 10.01.2011
comment
Трудно сказать, отладчик очень капризничает в отладке этого кода. Конечно, есть накладные расходы, это не простой вызов виртуального метода. На самом деле, он вызывает метод jitted Invoke(), который затем делает косвенный вызов преобразователь, который массирует фрейм стека и вызывает правильный вызывающий объект, в зависимости от того, является ли он делегатом или многоадресным делегатом. Последний должен перебирать список вызовов. Это довольно дешево, когда я его профилировал (наносекунды), но, конечно, не так дешево, как просто вызов метода. - person Hans Passant; 10.01.2011
comment
Спасибо Ганс. Внутренний материал очень полезный и интересный. - person richard; 10.01.2011
comment
Я видел некоторое время назад некоторые ориентиры по этому поводу. Однако я не могу найти его снова, но, принимайте это или нет, накладные расходы были на удивление малы. - person Eilistraee; 06.03.2011
comment
Это действительно должно упоминать свойства Target и Method; они крайне важны для понимания того, из чего состоит экземпляр делегата, ИМО. - person Roman Starkov; 04.01.2012
comment
Из любопытства, каковы преимущества того, что Delegate сам включает в себя функцию многоадресной рассылки, а не Delegate.Combine создает делегат, Target которого является массивом делегатов, а Method является методом, который вызывает все элементы в переданном массиве? - person supercat; 13.05.2013