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

Поддержка цепочек делегатов в с#

Компилятор С# облегчает жизнь разработчикам, автоматически предоставляя перегрузку операторов «+=» и «-=» для типов делегатов. Эти операторы вызывают методы Delegate.Combine и Delegate.Remove соответственно. Они упрощают построение цепочек делегатов. В результате компиляции методов ChainDelegateDemo1 и ChainDelegateDemo2 (см. пример в начале главы) получается абсолютно идентичный IL-код. Единственная разница в том, что исходный код ChainDelegateDemo2 проще за счет использования операторов «+=» и «-=» языка С#.

Расширенное управление цепочкой делегатов

Вы уже поняли, как строить цепочки объектов-делегатов и как вызывать все объекты в таком списке. Поскольку метод Invoke типа делегата включает код, итеративно перечисляющий все элементы массива, вызываются все элементы цепочки. Видно, что это очень простой алгоритм. Хотя для большинства сценариев достаточно и такого простого алгоритма, у него много ограничений. Например, отбрасываются все значения, возвращаемые методами обратного вызова, кроме последнего. Остальные значения, возвращаемые методами обратного вызова, невозможно получить при использовании простого алгоритма, но это ограничение — не единственное. А если один из вызванных делегатов генерирует исключение или надолго заблокируется? Поскольку алгоритм последовательно вызывает все делегаты цепочки, «проблема» с одним из делегатов в цепочке остановит вызов остальных. Ясно, что такой алгоритм ненадежен.

Для ситуаций, в которых такого алгоритма недостаточно, класс MulticastDelegate предлагает экземплярный метод GetlnvocationList, позволяющий явно вызвать любой из делегатов цепочки по любому алгоритму, соответствующему вашим потребностям:

public abstract class MulticastDelegate : Delegate

{

// Создает массив делегатов, в котором

// каждый элемент соответствует делегату цепочки.

public sealed override Delegated GetlnvocationList();

}

Метод GetlnvocationList принимает объект, производный от MulticastDelegate, и возвращает массив ссылок на массив ссылок на Delegate, в котором каждая ссылка указывает на один из объектов-делегатов цепочки.

Код GetlnvocationList создает массив и инициализирует его элементами, каждый из которых ссылается на один делегат в цепочке; в конце возвращается ссылка на массив. Если значение поля _invocationList равно null, возвращаемый массив содержит один элемент, который ссылается на единственный делегат цепочки, на экземпляр самого делегата.

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

using System;

using System.Text;

// Определяем компонент Light.

internal sealed class Light

{

// Этот метод возвращает состояние объекта Light.

public String SwitchPosition()

{

return "The light is off";

}

}

// Определяем компонент Fan.

internal sealed class Fan

{

// Этот метод возвращает состояние объекта Fan.

public String Speed()

{

throw new InvalidOperationException("The fan broke due to overheating");

}

}

// Определяем компонент Speaker.

internal sealed class Speaker

{

// Этот метод возвращает состояние объекта Speaker.

public String Volume()

{

return "The volume is loud";

}

}

public sealed class Program

{

// Определение делегатов, позволяющих запрашивать состояние компонента.

private delegate String GetStatus();

public static void Main()

{

// Объявляем пустую цепочку делегатов.

GetStatus getStatus = null;

// Создаем три компонента и добавляем в цепочку делегатов

// методы для проверки их состояния.

getStatus += new GetStatus(new Light().SwitchPosition);

getStatus += new GetStatus(new Fan().Speed);

getStatus += new GetStatus(new Speaker().Volume);

// Отображаем сводный отчет, отражающий

// состояние трех компонентов.

Console.WriteLine(GetComponentStatusReport(getStatus));

Console.ReadLine();

}

// Метод, запрашивающий несколько компонентов

// и возвращающий отчет об их состоянии.

private static String GetComponentStatusReport(GetStatus status)

{

// Если цепочка пуста, то делать нечего.

if (status == null)

return null;

// Построить отчет о состоянии.

StringBuilder report = new StringBuilder();

// Получаем массив, где каждый элемент - это делегат из цепочки.

Delegate[] arrayOfDelegates = status.GetInvocationList();

// Итеративно обрабатываем все делегаты массива.

foreach (GetStatus getStatus in arrayOfDelegates)

{

try

{

// Получаем строку с состоянием компонента и добавляем в отчет.

report.AppendFormat("{0}{1}{1}", getStatus(), Environment.NewLine);

}

catch (InvalidOperationException e)

{

// Генерируем в отчете запись об ошибке для этого компонента.

Object component = getStatus.Target;

report.AppendFormat(

"Failed to get status from {1}{2}{0}Error: {3}{0}{0}",

Environment.NewLine,

((component == null) ? "" : component.GetType() + "."),

getStatus.Method.Name,

e.Message

);

}

}

// Вернуть сводный отчет вызывающему коду.

return report.ToString();

}

}

Скомпоновав и запустив этот код, мы увидим:

The light is off

Failed to get status from Fan.Speed

Error: The fan broke due to overheating

The volume is loud