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

Использование делегатов для обратного вызова экземплярных методов

Делегаты позволяют вызывать экземплярные методы объекта. Чтобы понять, как работает обратный вызов экземплярного метода, взгляните на метод InstanceDelegateDemo.

Обратите внимание, что объект pтипа Program создается в методе InstanceDelegateDemo. У объекта Program нет экземплярных полей или свойств — я создал его просто для наглядности.

Когда при вызове Counter создается новый объект-делегат Feedback, его конструктору передается p.FeedbackToFile. Это заставляет делегат стать оболочкой для ссылки на метод FeedbackToFile, который является экземплярным, а не статическим методом.

Когда Counter обращается к методу обратного вызова, указанному в параметре fb, вызывается экземплярный метод FeedbackToFile, а адрес только что созданного объектаpпередается как явный параметр в метод FeedbackToFile.

Этот пример призван показать, что делегаты могут служить оболочкой как для экземплярных, так и для статических методов.

В случае экземплярного метода, делегат должен знать экземпляр объекта, который собирается обрабатывать вызываемый им метод.Создание оболочек экземплярных методов полезно, так как позволяет коду внутреннего объекта получить доступ к экземплярным членам объекта. Это означает, что у объекта может быть состояние, которое может использоваться во время работы метода обратного вызова.

Правда о делегатах

Может показаться, что использовать делегаты несложно: вы определяете делегат ключевым словом delegate языка C#, создаете его экземпляр с помощью знакомого оператора new и вызываете метод обратного вызова, пользуясь известным вам синтаксисом «вызова метода» (в котором вместо имени метода указывается переменная, ссылающаяся на объект-делегат).

Но на самом деле делегаты гораздо сложнее, чем показывают продемонстрированные примеры.

Давайте-ка еще раз изучим эту строку кода:

internal delegate void Feedback(Int32 value);

На самом деле, обнаружив такую строку, компилятор создает полное определение класса, которое выглядит примерно так:

internal class Feedback : System.MulticastDelegate

{

// Конструктор.

public Feedback(Object obj, IntPtr method) {}

// Метод, прототип которого задан в исходном тексте.

public virtual void Invoke(Int32 value){}

// Методы, обеспечивающие асинхронный обратный вызов.

public virtual IAsyncResult BeginInvoke(Int32 value, AsyncCallback callback, Object obj){}

public virtual void EndInvoke(IAsyncResult result){}

}

У определенного компилятором класса четыре метода:

  1. конструктор

  2. Invoke

  3. Beginlnvoke

  4. Endlnvoke

В этом примере компилятор создал класс Feedback, производный от типа SystemMulticastDelegate, определенного в библиотеке классов .NET Framework Class Library.

Внимание!Класс System.MulticastDelegate является производным от System.Delegate, который, в свою очередь, наследует класс System.Object.

Наличие двух классов делегатов — факт неприятный, но так уж сложилось исторически; в FCL должен быть лишь один класс делегата. К сожалению, нужно помнить об обоих классах, потому что, даже если вы будете в качестве типов делегатов выбирать в качестве базового класс MulticastDelegate, иногда придется работать с типами делегатов, используя методы, определенные в классе Delegate, а не MulticastDelegate. В частности, у класса Delegate есть статические методы Combine и Remove.

Сигнатуры обоих методов указывают, что они принимают параметры типа Delegate. Так как ваши типы делегатов являются производными от MulticastDelegate, который наследует Delegate, экземпляры вашего типа делегата можно передавать в эти методы.

Feedback закрытый класс, так как делегат был объявлен в исходном тексте как internal. Если бы он был объявлен в исходном тексте с модификатором public, то сгенерированный компилятором класс Feedback был бы соответственно открытым.

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

Поскольку все типы делегатов являются потомками MulticastDelegate, они наследуют все поля, свойства и методы MulticastDelegate. Наверное, самые важные из них — это три закрытых поля (табл. 15-1).

Табл. 15-1. Важнейшие закрытые поля MulticastDelegate

Поле

Тип

Описание

_target

System.Object

Когда объект-делегат служит оберткой статического метода, это поле содержит null. Когда объект-делегат служит оберткой экземплярного метода, это поле содержит ссылку на объект, с которым должны выполняться операции при вызове метода обратного вызова. Т.е. это поле содержит значение, которое нужно передать в явном параметре this экземплярного метода.

_methodPtr

System.Int32

Внутреннее целочисленное значение, используемое CLR для идентификации метода обратного вызова.

_prev

System.Multicast-

Delegate

Обычно содержит null. Оно может ссылаться на массив делегатов при создании цепочки делегатов.

У всех делегатов конструктор принимает два параметра — ссылку на объект и целое число, ссылающееся на метод обратного вызова. Но, изучив исходный текст, можно видеть, что я передаю такие значения, как Program.FeedbackToConsole или p.FeedbackToFile. Интуиция должна подсказать вам, что этот код не должен компилироваться!

Но компилятор знает, что создается делегат, и путем синтаксического анализа кода определяет объект и метод, на которые передается ссылка. Ссылка на объект передается в параметре object конструктора, а специальное значение IntPtr (получаемое из маркеров метаданных MethodDef или MethodRef), идентифицирующее метод, передается в параметре method. В случае статического метода в параметре object передается null. Внутри конструктора значения этих параметров сохраняются в закрытых полях _target и _methodPtr. Конструктор также заносит null в поле _invocationList.

Любой объект делегата на деле является оболочкой метода и объекта, обрабатываемого этим методом. Поэтому при наличии двух следующих строк кода:

Feedback fbStatic = new Feedback(Program.FeedbackToConsole);

Feedback fblnstance = new Feedback(new Program().FeedbackToFile);

переменные fbStatic и fblnstance ссылаются на два разных инициализированных объекта-делегата Feedback (рис. 15-2).

Рис. 15-2. Переменная, ссылающаяся на делегат статического метода, и переменная, ссылающаяся на делегат экземплярного метода

В классе Delegate определены два неизменяемых открытых экземплярных свойства: Target и Method. При наличии ссылки на объект делегата можно запросить значения этих свойств.

Target возвращает ссылку на объект, обрабатываемый при обратном вызове метода. В сущности, Target возвращает значение, хранимое в закрытом поле _target. Если этот метод — статический, Target возвращает null.

Свойство Method возвращает объект SystemReflectionMethodlnfo, описывающий метод обратного вызова. В сущности, у свойства Method есть внутренний механизм, который преобразует значение из закрытого поля _methodPtr в объект Methodlnfo и возвращает его.

Эту информацию можно использовать по-разному, например, чтобы проверить, не ссылается ли объект делегата на экземплярный метод определенного типа:

Boolean DelegateRefersToInstanceMethodOfType(MulticastDelegate d, Type type)

{

return ((d.Target != null) && d.Target.GetType() == type);

}

Можно также написать код, проверяющий имя метода обратного вызова (например, FeedbackToMsgBox):

Boolean DelegateRefersToMethodOfName(MulticastDelegate d, String methodName)

{

return (d.Method.Name == methodName);

}

Есть еще масса других ситуаций, в которых могут быть полезными эти свойства.

Поговорим о том, как вызываются методы обратного вызова. Для удобства я еще раз покажу код метода Counter.

private static void Counter(Int32 from, Int32 to, Feedback fb)

{

for (Int32 val = from; val <= to; val++)

{

// Если указаны какие-либо методы обратного вызова, вызвать их.

if (fb != null)

fb(val);

}

}

Посмотрим повнимательнее на строку кода, следующую сразу после комментария. Оператор if сначала проверяет, не равна ли null переменная fb. Если нет, выполняется следующая строка, вызывающая метод обратного вызова. Проверка на неравенство null нужна, потому что fb — всего лишь переменная, которая может ссылаться на объект-делегат Feedback, но может быть и null.

Может показаться, что я вызываю функцию fb, передавая ей один параметр (val), но такой функции нет. И в этом случае компилятор генерирует код для вызова метода Invoke объекта-делегата, поскольку знает, что fb — это переменная, ссылающаяся на объект-делегат. Иначе говоря, обнаружив строку:

fb (val);

компилятор генерирует код, как если бы он увидел в исходном тексте следующее: fb.Invoke(val);

Вообще, можно в Counter вызывать Invoke явно, как здесь:

private static void Counter(Int32 from, Int32 to, Feedback fb)

{

for (Int32 val = from; val <= to; val++)

{

// Если указаны какие-либо методы обратного вызова, вызвать их.

if (fb != null)

fb.Invoke(val);

}

}

Наверное, вы помните, что компилятор определил метод Invoke при определении класса Feedback. При вызове метода Invoke он использует поля _target и _methodPtr для вызова желаемого метода на заданном объекте.

Заметьте: сигнатура метода Invoke соответствует такой же сигнатуре делегата, то есть и делегат Feedback, и (созданный компилятором) метод Invoke принимают параметр типа Int32 и возвращают void.