Внутреннее устройство делегатов

Описание полного класса делегата и асинхронной обработки

Аудитория

Данная статья предназначена для людей, имеющих базовые знания о делегатах в C#.

Введение

Все знают о слове “делегат” в сфере .NET. Но что станет делать компилятор, увидев слово “делегат” в вашей программе? Компилятор и CLR (общеязыковая среда исполнения) выполняют много закулисной обработки, чтобы скрыть сложность. Здесь рассматривается, как компилятор и CLR работают вместе, чтобы реализовать делегаты. Данная статья описывает полный класс делегата и асинхронную обработку, выполняемую делегатом.

Использование кода

public delegate void MyDelegate(int intValue);

На самом деле компилятор определяет полный класс, выглядящий примерно так:

class MyDelegate : System.MulticastDelegate
{
    //конструктор
    public  MyDelegate(Object object, Inptr method);

    //такой же прототип, что и заданный в исходном коде
    public virtual void Invoke(Int32 intValue);

    //методы делегатов могут вызываться асинхронно
    public virtual IAsyncResult BeginInvoke(Int32 intValue,
            AsyncCallback callback, object object);

    public virtual void EndInvoke(IAsyncResult result);
}

ILdasm.exe отображает метаданные, созданные компилятором для делегата.

Определенный компилятором класс имеет четыре метода:
•    конструктор
•    метод Invoke (вызвать)
•    метод BeginInvoke
•    метод EndInvoke

Метод Invoke применяется для вызова целевого метода в том же потоке. Это синхронный процесс. Методы BeginInvoke и EndInvoke описаны дальше в этой статье. Перед этим нужно разобраться, как на методы (статические и экземпляра) устанавливаются указатели внутри делегата – пожалуй, это самая важная функция в делегатах. Есть три закрытых поля:

1.    _target
Когда объект делегата обертывает метод static - данное поле пустое. Когда объект делегата обертывает метод экземпляра - данное поле ссылается на объект.
2.    _methodptr
Оно используется для определения метода, который надлежит вызвать обратно.
3.    _invocationlist
Данное поле обычно пустое, оно может ссылаться на массив делегатов при строительстве цепочки делегатов.

MyDelegate objStatic     =  new MyDelegate(Class.StaticMethod);
MyDelegate objInstance =  new MyDelegate(new Class().InstanceMethod);

Поле _target содержит тип System.Object. Ссылка на объект передается в параметр объекта конструктора, и специальное значение intptr, указывающее на метод, передается в параметр метода. Для статических методов в параметр объекта передается null (указатель на пустой объект). Внутри конструктора эти два аргумента сохраняются в закрытых полях _target и _methodptr соответственно.

Внутри метод combine (объединить) видит, что objChain уже ссылается на объект делегата, поэтому combine создаст новый объект делегата. Этот новый объект делегата инициализирует свои поля private _target и _methodptr значениями, не рассматриваемыми здесь. Здесь важно, что поле _invocationlist инициализируется ссылкой на массив объектов делегатов. Первый элемент массива (индекс 0) будет инициализирован ссылкой на делегат, обертывающий статический метод. Второй элемент массива (индекс 1) будет инициализирован ссылкой на делегат, обертывающий метод экземпляра.

MyDelegate objStatic1     =  new MyDelegate(Class.StaticMethod1);
MyDelegate objInstance2 =  new MyDelegate(new Class().InstanceMethod2);



MyDelegate objChain = null;
objChain = (MyDelegate) Delegate.Combine( objChain, objStatic1);
objChain = (MyDelegate) Delegate.Combine( objChain, objInstance2);    


Асинхронные делегаты

Делегаты могут вызывать методы асинхронным способом. Если вызван метод BeginInvoke, то CLR поставит запрос в очередь и сразу же вернется в основной поток. Целевой метод будет вызван в потоке из пула потока. Основной поток может выполнять параллельное выполнение в целевом методе. Метод BeginInvoke имеет три параметра:

1.    Входной параметр
2.    Экземпляр делегата AsyncCallBack
3.    Информация об асинхронном состоянии
    objDel.BeginInvoke(5,new AsyncCallback(MyCallback),"Result From Main Program");

Последние два параметра используются для предоставления механизма обратного вызова, который будет вызван после выполнения целевого метода. Это вызывается делегат AsyncCallBack.
delegate void AsyncCallback( IAsyncResult ar );

Если обратный вызов не был указан в BeginInvoke, то EndInvoke используется в исходном потоке. Он используется для получения возвращаемого значения для асинхронной обработки. Если обратный вызов имел место, то EndInvoke помещается в метод обратного вызова.
Ниже приведен пример программы для асинхронного делегата:

public delegate int MyDelegate(int intX);

class Program
{
    static void Main(string[] args)
    {
        MyClass objClass = new MyClass();
        MyDelegate objDel = new MyDelegate(objClass.MyMethod);

        //Асинхронная обработка начинается здесь
        IAsyncResult AsyncRes = objDel.BeginInvoke
        (5, new AsyncCallback(MyCallBack), "Информация о состоянии из основного потока");

        Console.WriteLine("Из основного потока ");

        Console.ReadLine();
    }

    public static void MyCallBack(IAsyncResult ar)
    {
        AsyncResult Result = (AsyncResult)ar;
        MyDelegate objDel = (MyDelegate)Result.AsyncDelegate;
        int intResult = objDel.EndInvoke(ar);
        Console.WriteLine("Вывод из обратного вызова: " + intResult);

        //информация о состоянии из основного потока
        string strMessage = (string)ar.AsyncState;
        Console.WriteLine(strMessage);
    }
}

class MyClass
{
    public int MyMethod(int intX)
    {
        Thread.Sleep(10000);
        return (intX * intX);
    }
}       


Вывод


Заключение

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