Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы ОСиСП.doc
Скачиваний:
68
Добавлен:
11.05.2015
Размер:
1.78 Mб
Скачать

Упрощение синтаксиса работы с делегатами в с#

Большинство программистов считает, что работать с делегатами слишком обременительно. Причина в очень сложном синтаксисе. Возьмем к примеру такую строку кода:

button1.Click += new EventHandler(button1_Click);

где button1.Click — это метод, выглядящий примерно так:

void button1_Click(Object sender, EventArgs e)

{

// Кнопка щелкнута - нужно выполнять соответствующие действия...

}

Идея первой строки кода — зарегистрировать адрес метода button1_Click в элементе управления «кнопка» с тем, чтобы по щелчку кнопки вызывался этот метод. Большинству программистов кажется совершенно неестественным создавать объект-делегат EventHandler только для того, чтобы определить адрес метода button1_Click. Однако создание объекта-делегата EventHandler необходимо CLR, так как этот объект служит оберткой, которая обеспечивает вызов метода со строгим соблюдением безопасности типов. Обертка также поддерживает вызов экземплярных методов и создание цепочек делегатов. К сожалению, большинство программистов не хочет думать о таких деталях. Программисты предпочли бы записать приведенный выше код так:

button1.Click += button1_Click;

К счастью, компилятор С# от Microsoft поддерживает несколько способов упрощения синтаксиса при работе с делегатами. Сейчас я об этом расскажу поподробнее, но, прежде чем начать, сделаю одно важное замечание. То, что вы сейчас узнаете, — всего лишь ряд упрощений синтаксиса в C#, которые облегчают задачу программистам по созданию IL-кода, который необходим для нормальной работы CLR и других языков программирования с делегатами. Это также означает, что все сказанное ниже относится исключительно кC# — другие компиляторы скорее всего не поддерживают такой упрощенный синтаксис делегатов.

Упрощенный синтаксис № 1: не нужно создавать объект-делегат

Как я уже показывал, С# позволяет определять имя метода обратного вызова, не создавая объект-делегат, служащий оберткой. Вот еще один пример:

internal sealed class AClass

{

public static void CallbackWithoutNewingADelegateObject()

{

ThreadPool.QueueUserWorkItem(SomeAsyncTask, 5);

}

private static void SomeAsyncTask(Object o)

{

Console.WriteLine(o);

}

}

Здесь статический метод QueueUserWorkltem класса ThreadPool ожидает ссылку на объект-делегат WaitCallback, который в свою очередь содержит ссылку на метод SomeAsyncTask. Так как компилятор С# сам «догадывается», что имеется в виду, я могу опустить код создания объекта-делегата WaitCallback, что делает код намного читабельнее и понятнее. Конечно, в процессе компиляции компилятор С# автоматически создает IL-код создания нового объекта-делегата WaitCallback.

Упрощенный синтаксис № 2: не нужно определять метод обратного вызова

В приведенном выше коде имя метода обратного вызова, SomeAsyncTask, передается методу QueueUserWorkltem класса ThreadPool. С# позволяет встраивать реализацию метода обратного вызова непосредственно в код, а не в сам метод. Например, приведенный выше код можно переписать так.

internal sealed class AClass

{

public static void CallbackWithoutNewingADelegateObject()

{

ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);

}

}

Заметьте: первый «параметр» метода QueueUserWorkltem представляет собой блок кода! Обнаружив ключевое слово delegate в месте, где ожидается ссылка на объект- делегат, компилятор С# автоматически определяет в классе новый закрытый метод (здесь это класс AClass). Этот новый метод называют анонимным из-за того, что компилятор создает имя метода автоматически и обычно вы даже никогда не узнаете его имя. Однако можно прибегнуть к ILDasm.exe, чтобы изучить сгенерированный компилятором код. После компиляции приведенного выше кода с помощью ILDasm.exe я увидел, что компилятор С# решил назвать этот метод <CallbackWithoutNewingADelegateObject>b__0. Но будьте осторожны: никогда не стоит рассчитывать на то, что компилятор создаст именно такое имя метода, потому что в следующей версии компилятора С# алгоритм генерации имени метода может измениться.

Используя ILDasm.exe, также можно заметить, что компилятор С# применяет к этому методу атрибут SystemRuntime.CompilerServices.CompilerGeneratedAttribute для указания того, что метод создан компилятором, а не программистом. Код из «параметра» размещается в сгенерированном компилятором методе.

ПримечаниеЧисло операторов или их типов, которые можно использовать в коде обратного вызова (анонимном методе), не ограничено. Однако при создании анонимного метода никак нельзя применить к методу нестандартный (пользовательский) атрибут. Более того — по отношению к методу нельзя применить никаких модификаторов метода (например, unsafe).

Сгенерированные компилятором анонимные методы практически всегда закрыты, а метод — статический или нестатический, в зависимости от того, к каким членам экземпляра обращается метод. Так что нет никакой нужды применять к методу такие модификаторы, как public, protected, internal, virtual, sealed, override и abstract.

Наконец, при компиляции приведенного выше кода компилятор C# перепишет код примерно так:

internal sealed class AClass

{

// Это закрытое поле создается для кеширования объекта-делегата.

// Преимущество: CallbackWithoutNewingADelegateObject не будет создавать

// новый объект при каждом вызове.

// Недостаток: объект в кеше никогда не убирается сборщиком мусора.

[CompilerGenеrated]

private static WaitCallback <>9__CachedAnonymousMethodDelegate1;

public static void CallbackWithoutNewingADelegateObject()

{

if (<>9__CachedAnonymousMethodDelegate1 == null)

{

// При первом вызове объект-делегат создается и кешируется.

<>9__CachedAnonymousMethodDelegate1 =

new WaitCallback(<CallbackWithoutNewingADelegateObject>b__0);

}

ThreadPool.QueueUserWorkItem(<>9__CachedAnonymousMethodDelegate1, 5);

}

[CompilerGenеrated]

private static void <CallbackWithoutNewingADelegateObject>b__0(Object obj)

{

Console.WriteLine(obj);

}

}

Прототип анонимного метода должен соответствовать типу делегата WaitCallback: возвращать void и принимать параметр Object. Однако я создавал анонимный метод, поэтому определил имя параметра, разместив (Object obj) после ключевого слова delegate.

Анонимный метод объявляется закрытым; это запрещает любому коду, за исключением кода типа, доступ к методу (хотя отражение позволит узнать, что метод существует). Заметьте также, что анонимный метод определен как статический, потому что код не обращается ни к каким членам экземпляра (да и не сможет этого сделать, так как CallbackWithoutNewingADelegate-Object — сам по себе статический метод). Однако код может сослаться на любые определенные в классе статические поля или статические методы. Вот пример:

internal sealed class AClass

{

private static String sm_name; // Статическое поле.

public static void CallbackWithoutNewingADelegateObject()

{

ThreadPool.QueueUserWorkItem(

// Код обратного вызова может ссылаться на статические члены.

delegate(Object obj) { Console.WriteLine(sm_name + ": " + obj); }, 5

);

}

}

Если бы метод CallbackWithoutNewingADelegateObject не был статическим, код анонимного метода мог бы содержать ссылки на члены экземпляра. Но даже в отсутствие в коде ссылок на члены экземпляра компилятор создаст статический анонимный метод, так как он эффективнее, чем экземплярный метод, потому что дополнительный параметр this не нужен. Но, если код анонимного метода действительно ссылается на член экземпляра, компилятор создаст нестатический анонимный метод:

internal sealed class AClass

{

private String m_name; // Зкземплярное поле.

// Экземплярный метод.

public void CallbackWithoutNewingADelegateObject()

{

ThreadPool.QueueUserWorkItem(

// Код обратного вызова может ссылаться на члены экземпляра.

delegate(Object obj) { Console.WriteLine(m_name + ": " + obj); }, 5

);

}

}