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

Использование делегатов для обратного вызова множественных методов (цепочки делегатов)

Цепочка делегатов (chaining) — это набор объектов-делегатов, которой позволяет вызывать все методы, представленные делегатами набора.

Чтобы лучше понять принцип работы цепочки, обратите внимание на метод ChainDelegateDemo1. В нем после оператора ConsoleWriteLine я создаю три объекта-делегата, на которые соответственно ссылаются три переменные fb1,fb2 и fb3 (рис. 15-4).

Рис. 15-4. Начальное состояние объектов-делегатов, на которые ссылаются три переменные fbl,fb2 и fb3.

Переменная-ссылка на объект-делегат Feedback должна ссылаться на цепочку или набор делегатов, служащих оболочками методам обратного вызова. Инициализация fbChain значением null говорит об отсутствии методов обратного вызова. Открытый статический метод Combine класса Delegate используется для добавления делегата в цепочку:

fbChain = (Feedback)Delegate.Combine(fbChain, fb1);

При выполнении этой строчки кода метод Combine «видит» попытку объединить null и fb1. Код метода Combine просто возвращает значение fb1, а в переменной fbChain размещается ссылка на тот же объект-делегат, на который ссылается fb1 (рис. 15-5).

Рис. 15-5- Состояние объектов-делегатов после добавления в цепочку второго делегата

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

fbChain = (Feedback)Delegate.Combine(fbChain, fb2);

Код метода Combine «видит», что fbChain уже ссылается на объект-делегат, и поэтому создает новый объект-делегат. Новый объект-делегат инициализирует свои закрытые поля _target и _methodPtr.

Поле _invocationList инициализируется ссылкой на массив объектов-делегатов.

Первый элемент массива (с индексом 0) инициализируется ссылкой на делегат, служащий оберткой метода FeedbackToConsole (это делегат, на который сейчас ссылается fbChain). Второй элемент массива (индекс 1) инициализируется ссылкой на делегат, служащий оберткой метода FeedbackToMsgBox (на этот делегат ссылается fb2). Наконец, переменной fbChain присваивается ссылка на вновь созданный объект-делегат (рис. 15-6).

Рис. 15-6. Состояние объектов-делегатов после добавления в цепочку второго делегата

Для создания третьего делегата снова вызывается метод Combine.

fbChain = (Feedback)Delegate.Combine(fbChain, fb3);

Видя, что fbChain уже ссылается на объект-делегат, Combine создает новый объект-делегат (рис. 15-7).

Как и раньше, новый объект-делегат инициализирует свои закрытые поля _target и _methodPtr какими-то значениями, а поле _invocationList инициализируется ссылкой на массив объектов-делегатов.

Первый и второй элементы массива (индексы 0 и 1) инициализируются ссылками на те же делегаты, на которые ссылался предыдущий объект-делегат в массиве. Третий элемент массива (индекс 2) инициализируется ссылкой на делегат, служащий оберткой метода FeedbackToFile (на этот делегат ссылается fb3). Наконец, переменной fbChain присваивается ссылка на вновь созданный объект-делегат.

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

Рис. 15-7. Окончательное состояние объектов-делегатов в готовой цепочке

После выполнения всего кода создания цепочки, переменная fbChain передается методу Counter.

Counter(1, 2, fbChain);

Внутри Counter содержится код, неявно вызывающий метод Invoke по отношению к объекту-делегату Feedback. Когда Invoke вызывается по отношению к делегату, на который ссылается fbChain, делегат обнаруживает, что поле _invocationList не равно null, и инициируется выполнение цикла, итеративно обрабатывающего все элементы массива, путем вызова метода, оболочкой которого служит указанный делегат.

У нас методы вызываются в следующей последовательности: FeedbackToConsole, FeedbackToMsgBox и FeedbackToFile.

В псевдокоде метод Invoke класса Feedback выглядит примерно так:

public void Invoke(Int32 value)

{

Delegate[] delegateSet = _invocationList as Delegate[];

if (delegateSet != null)

{

// Этот массив делегатов указывает делегаты, которые нужно вызвать.

foreach (Feedback d in delegateSet)

d(value);

// Вызываем каждый делегат.

}

else

{

// Этот делегат определяет один метод,

// который нужно вызвать по механизму обратного вызова.

// Вызвать метод обратного вызова для указанного объекта.

_methodPtr.Invoke(_target, value);

// Предыдущая строка - очень приблизительная копия реального кода.

// Происходящее на самом деле не поддается

// иллюстрации средствами С#.

}

}

Делегаты можно удалять из цепочки, вызывая статический метод Remove объекта Delegate.

В конце кода метода ChainDelegateDemo1 есть пример:

fbChain = (Feedback)Delegate.Remove(fbChain, new Feedback(FeedbackToMsgBox));

Метод Remove сканирует массив делегатов (с конца и до члена с индексом 0), поддерживаемых объектом-делегатом, на который ссылается первый параметр (в нашем примере fbChain). Remove ищет делегат, поля _target и _methodPtr которого совпадают с соответствующими полями второго параметра (в нашем примере нового делегата Feedback). Если Remove находит совпадение и в массиве остается более одного элемента, создается новый объект-делегат — создается массив _invocationList и инициализируется ссылкой на все элементы исходного массива за исключением удаляемого элемента — и возвращается ссылка на новый объект-делегат. При удалении последнего элемента в цепочке Remove возвращает null.

Заметьте:за раз Remove удаляет из цепочки лишь один делегат, а не все делегаты с заданными значениями в полях _target и _methodPtr.

Пока мы рассматривали тип-делегат Feedback, возвращающий значение void. Однако его можно было определить и так:

internal delegate Int32 Feedback(Int32 value);

В этом случае в псевдокоде метод Invoke выглядел бы примерно так:

public Int32 Invoke(Int32 value)

{

Int32 result;

Delegate[] delegateSet = _invocationList as Delegate[];

if (delegateSet != null)

{

// Этот массив делегатов указывает делегаты, которые нужно вызвать.

foreach (Feedback d in delegateSet)

result = d(value); // Вызываем каждый делегат.

}

else

{

// Этот делегат определяет один метод,

// который нужно вызвать по механизму обратного вызова.

//Вызвать метод обратного вызова для указанного объекта.

result = _methodPtr.Invoke(_target, value);

// Предыдущая строка - очень приблизительная копия реального кода.

// Происходящее на самом деле не поддается

// иллюстрации средствами С#.

}

return result;

}

По мере вызова отдельных делегатов массива возвращаемое значение сохраняется в переменной result. По завершении цикла переменная result содержит результат только последнего вызванного делегата (предыдущие возвращаемые значения отбрасываются); это значение возвращается вызывающему коду, вызвавшему Invoke.