Всегда хочется разбираться в фишках того языка программирования, который ты используешь ежедневно. Такие упрощенные трактовки делегатов, которые озвучены во многих информационных источниках могут вызвать в умах, интересующихся деталями, людей много вопросов.
Чтобы поэтапно разобраться с тем, что же такое делегаты, для начала давайте обратимся к наиболее популярным источникам, к которым зачастую в первую очередь обращаются новички (и не только).
Определение делегата с официального сайта Microsoft: Делегат — это тип, представляющий ссылки на методы с определенным списком параметров и типом возвращаемого значения.
Что говорит метанит: Делегаты представляют такие объекты, которые указывают на методы. То есть делегаты - это указатели на методы и с помощью делегатов мы можем вызвать данные методы.
Из обоих трактовок можно сделать одно заключение, что делегат — это тип-указатель на метод.
Давайте разберемся с тем, что же это за тип такой. Рассмотрим механизм объявления делегата:
namespace Delegate
{
public delegate void Delegate();
internal class Program
{
static void Main(string[] args)
{
}
}
}
В примере выше мы видим: для того, чтобы дать понять компилятору что мы хотим использовать делегат, то после модификатора доступа мы используем зарезервированное слово delegate а далее все очень похоже на объявление метода: тип возвращаемого значения (void), название и в круглых скобках аргументы (в нашем примере их нет). Не вникая в детали, можно сказать, что делегат – это специальная сущность как классы, методы, пространства имен и так далее.
Теперь рассмотрим пример работы с делегатами:
namespace Delegate
{
public delegate void Delegate();
internal class Program
{
static void Main(string[] args)
{
Delegate obj;
}
}
}
В примере выше мы объявили переменную obj типа Delegate как и с любым другим классом.
Далее для использования функционала методов нашего типа Delegate нам нужен объект, который мы можем сложить в нашу переменную. Для инициализации объекта в каждом делегате предусмотрен один конструктор который на вход принимает… метод. Но с одной лишь оговоркой: сигнатура этого метода должна быть идентичной сигнатуре типа делегата (не зря его объявление похоже на объявление метода).
namespace Delegate
{
public delegate void Delegate();
internal class Program
{
void Main(string[] args)
{
Delegate obj = new Delegate(Method);
}
public void Method()
{
}
}
}
В примере выше мы инициализировали объект типа нашего делегата. Как и с обычным объектом класса. Теперь мы можем пользоваться методами нашего типа делегата, основой – это метод Invoke. Он позволяет вызвать тот метод, который мы передали аргументом в конструктор при инициализации объекта типа делегата.
namespace Delegate
{
public delegate void Delegate();
internal class Program
{
void Main(string[] args)
{
Delegate obj = new Delegate(Method);
obj.Invoke();
}
public void Method()
{
}
}
}
Также можно вызвать метод и без помощи Invoke()
namespace Delegate
{
public delegate void Delegate();
internal class Program
{
void Main(string[] args)
{
Delegate obj = new Delegate(Method);
obj();
}
public void Method()
{
}
}
}
Мы воспользовались объектом типа делегата как методом. Вот основная фишка делегатов в языке C#. Следовательно, делегат предоставляет нам необычный механизм типов и их экземпляров для упаковки метода в объект.
Но давайте взглянем внутрь C# и посмотрим, что у делегата внутри – воспользуемся утилитой ildasm. Что же мы видим:
А мы видим, что делегат – это обычный тип, с конструктором (.ctor) и тремя методами: BeginInvoke, EndInvoke и Invoke.
Давайте подробнее рассмотрим конструктор нашего делегата. Первым аргументом он принимает «object», что является объектом типа System.Object, а вторым аргументом является native int. Так как в этой статье мы немного философствуем, то обратимся к переводу слова native – родной, то есть родной int. Родиной такого инта является наша система – 32 или 64 битная. То есть это такой инт, который привязан к нашей системе. Это целое число, представляющее собой указатель на что угодно, т.е. хранящее в себе адрес. В .NET используется специальная структура данных, а не просто int, чтобы обеспечить совместимость между 32-битными и 64-битными системами.
В языке C#, такой инт находит свое отражение в структуре System.IntPtr, а если развернуть название, то это integer pointer – целочисленный указатель.
Таким образом первым аргументом передается объект, который предоставляет нам метод, а вторым аргументом передается указатель на этот метод (адрес в памяти, где находится этот метод).
Рассмотрим процесс создания объекта типа делегата на следующем примере: у нас имеется объект obj1 типа Type1 с методом SomeAction() мы создаем объект типа делегата DelegateType и передаем ему ссылку на метод SomeAction() объекта obj1.
ldloc obj1
ldftn instance void Type1:: SomeAction ()
newobj void DelegateType::.ctor(object, native int)
stloc delegateInstance
Рассмотрим пошагово:
Командой ldloc объект obj1 загружается в стек вычислений;
Командой ldftn указатель на метод перемещается в стек вычислений;
Командой newobj создается новый объект типа DelegateType и помещается в стек вычислений;
Командой stloc извлекается объект delegateInstance типа DelegateType из стека вычислений и сохраняет его в списке локальных переменных.
Сразу все аргументы конструктора помещаются в стек, а затем создается новый объект указанного типа и ему передаются аргументы, которые ранее были помещены в стек.
Стоит отметить, что делегат возможно использовать со статическими методами, тогда первым аргументом в конструктор передается null.
Таким образом с помощью ключевого слова delegate мы даем понять компилятору о, том, что нам нужно создать тип с таким вот хитрым механизмом по сохранению ссылок на методы инкапсулированным внутри объекта.
Надеюсь, что данная публикация помогла вам взглянуть на делегаты по-новому.
Комментарии (5)
a-tk
27.03.2022 15:27+3А где внутренности? Где multicast и что это вообще за зверь такой и вообще? Где комбинирование делегатов? Где ссылки на статический и экземплярный метод?
Что вообще это было-то?PS: Рихтера таки читать надо было.
alega_13 Автор
27.03.2022 15:31Привет!
Так вот у Рихтера это и было, я описал то, чего там не было (в комменте выше и описал конкретно что я добавил). Смысла в его дословном цитировании или перефразировании не было бы.
Спасибо)
lair
27.03.2022 15:53+4Надеюсь, что данная публикация помогла вам взглянуть на делегаты по-новому.
Неа, не помогло. Сухой остаток статьи: "в объект специального класса с помощью синтаксического сахара загружается адрес метода". Ну и что?..
questor
Пересказ официальной документации, сайта метанит - и в чём смысл? Там же можно и прочитать, если бы вы привели ссылку. Аналогично можно открыть любую книгу по C# Рихтера (ну или кто вам больше нравится) и прочитать там то же самое.
Статья краткая, ничего нового в ней нет. Прямо говоря не вижу смысла в таких статьях на хабре => один из минусов мой.
alega_13 Автор
Здравствуй!
Спасибо за твой комментарий. Хочу немного пояснить:
Разобьем статью на две части: пример использования делегата и его "внутренности". Первая часть является - "Пересказ официальной документации, сайта метанит", но нужно было сделать краткое интро (с более формальными комментарии для новичка в отрасли) для того чтобы перейти к основной теме статьи, которая отражена в названии.
Вторая часть, которую можно условно начать со скрина ildasm'a - "Аналогично можно открыть любую книгу по C# Рихтера (ну или кто вам больше нравится) и прочитать там то же самое": для примера возьмем Рихтера. Да, там есть скриншот из ildasm'a, но описания работы создания экземпляра (и уж тем более подробного описания аргументов) делегата там, к сожалению, нету.
Целью статьи было показать читателю, для которого делегат - ссылка, что все несколько хитрее. Возможно, можно было в начале статьи более явно указать на цель и основой посыл - учту.
Также во второй части статьи идет основной поток информации сформированный на основе не "официальной документации, сайта метанит" или "любую книгу по C#", а документации ECMA-334.
Спасибо за твой комментарий!