Разбираемся во внутреннем устройстве делегатов в языке C#

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всегда хочется разбираться в фишках того языка программирования, который ты используешь ежедневно. Такие упрощенные трактовки делегатов, которые озвучены во многих информационных источниках могут вызвать в умах, интересующихся деталями, людей много вопросов. 

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

Определение делегата с официального сайта 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 мы даем понять компилятору о, том, что нам нужно создать тип с таким вот хитрым механизмом по сохранению ссылок на методы инкапсулированным внутри объекта. 

Надеюсь, что данная публикация помогла вам взглянуть на делегаты по-новому.

Источник: https://habr.com/ru/post/657635/


Интересные статьи

Интересные статьи

Подходы к обмену данными об угрозах находятся в активной фазе формирования и стандартизации. Сегодня есть пара значимых стандартов — MISP и STIX — и целая плеяда менее зн...
На работе я занимаюсь поддержкой пользователей и обслуживанием коробочной версии CRM Битрикс24, в том числе и написанием бизнес-процессов. Нужно отметить, что на самом деле я не «чист...
Кто бы что ни говорил, но я считаю, что изобретение велосипедов — штука полезная. Использование готовых библиотек и фреймворков, конечно, хорошо, но порой стоит их отложить и создать ...
Согласно многочисленным исследованиям поведения пользователей на сайте, порядка 25% посетителей покидают ресурс, если страница грузится более 4 секунд.
Практически все коммерческие интернет-ресурсы создаются на уникальных платформах соответствующего типа. Среди них наибольшее распространение получил Битрикс24.