КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Расширенное управление цепочкой делегатовВы уже поняли, как строить цепочки объектов-делегатов и как вызывать все объекты в таком списке. Поскольку метод 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
Упрощение синтаксиса работы с делегатами в С# Большинство программистов считает, что работать с делегатами слишком обременительно. Причина в очень сложном синтаксисе. Возьмем к примеру такую строку кода: 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# — другие компиляторы скорее всего не поддерживают такой упрощенный синтаксис делегатов.
|