пятница, 7 января 2022 г.

Делегаты

 

Зачем нам нужны делегаты


Исторически Windows API часто использовал указатели функций в стиле C для создания функций обратного вызова. Используя обратный вызов, программисты могли настроить одну функцию для отправки отчета другой функции в приложении. Таким образом, целью использования обратного вызова является обработка действий, связанных с нажатием кнопки, выбором меню и перемещением мыши. Но проблема с этим традиционным подходом заключается в том, что функции обратного вызова не были типобезопасными. В среде .NET обратные вызовы по-прежнему возможны с использованием делегатов с более эффективным подходом. Делегаты сохраняют три важные части информации, как показано ниже: 
  • Параметры метода.
  • Адрес вызываемого метода.
  • Тип возвращаемого значения метода.

Делегат — это решение для ситуаций, в которых вы хотите передать методы другим методам. Вы настолько привыкли передавать данные методам в качестве параметров, что идея передачи методов в качестве аргумента вместо данных может показаться немного странной. Однако бывают случаи, когда у вас есть метод, который что-то делает, например, вызывает какой-то другой метод. Вы не знаете во время компиляции, что это за второй метод. Эта информация доступна только во время выполнения, поэтому Делегаты — это средство для преодоления таких сложностей.

Обзор делегатов


Делегат — это абстракция одного или нескольких указателей на функции (существующих в C++; объяснение этого выходит за рамки этой статьи). В .NET реализована концепция указателей функций в виде делегатов. С делегатами вы можете обращаться с функцией как с данными. Делегаты позволяют передавать функции в качестве параметров, возвращать из функции в виде значения и сохранять в массиве. Делегаты обладают следующими характеристиками: 

  • Делегаты являются производными от класса System.MulticastDelegate.
  • У них есть подпись и возвращаемый тип. Функция, добавляемая к делегатам, должна быть совместима с этой сигнатурой.
  • Делегаты могут указывать на статические или экземплярные методы.
  • После создания объекта делегата он может динамически вызывать методы, на которые он указывает, во время выполнения.
  • Делегаты могут вызывать методы синхронно и асинхронно.

Делегат содержит несколько полезных полей. Первый содержит ссылку на объект, а второй содержит указатель на метод. Когда вы вызываете делегат, метод экземпляра вызывается для содержащейся ссылки. Однако, если ссылка на объект имеет значение null, среда выполнения понимает, что это означает, что метод является статическим. Более того, синтаксический вызов делегата аналогичен вызову обычной функции. Поэтому делегаты идеально подходят для реализации обратных вызовов.

 

Определение делегата


Делегат может быть определен как тип делегата. Его определение должно быть похоже на сигнатуру функции. Делегат может быть определен в пространстве имен и внутри класса. Делегат нельзя использовать в качестве члена данных класса или локальной переменной внутри метода. Прототип для определения типа делегата выглядит следующим образом.

тип возвращаемого значения делегата доступности имя_делегата (список параметров);

Объявления делегатов выглядят почти так же, как объявления абстрактных методов, вы просто заменяете ключевое слово abstract на ключевое слово delegate. Ниже приведено допустимое объявление делегата: 



  1. public delegate int operation(int x, int y);   

Когда компилятор C# встречает эту строку, он определяет тип, производный от MulticastDelegate, который также реализует метод с именем Invoke, который имеет точно такую ​​же сигнатуру, как метод, описанный в объявлении делегата. Для всех практических целей этот класс выглядит следующим образом:

  1. public class operation : System.MulticastDelegate  
  2. {  
  3.     public double Invoke(int x, int y);  
  4.     // Other code  
  5. }  

Как вы можете видеть с помощью ILDASM.exe, сгенерированный компилятором класс операции определяет три общедоступных метода, как показано ниже:

ILDASM
Figure 1.1


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

Образец программы делегатов


Реализация делегата может вызвать большую путаницу при первом знакомстве. Таким образом, это отличная идея, чтобы понять, создав этот пример программы как: 
  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     // Delegate Definition  
  6.     public delegate int operation(int x, int y);  
  7.          
  8.     class Program  
  9.     {  
  10.         // Method that is passes as an Argument  
  11.         // It has same signature as Delegates   
  12.         static int Addition(int a, int b)  
  13.         {  
  14.             return a + b;  
  15.         }  
  16.   
  17.         static void Main(string[] args)  
  18.         {  
  19.             // Delegate instantiation  
  20.             operation obj = new operation(Program.Addition);  
  21.    
  22.             // output  
  23.             Console.WriteLine("Addition is={0}",obj(23,27));   
  24.             Console.ReadLine();    
  25.         }  
  26.     }  
  27. }  

Здесь мы определяем делегат как тип делегата. Важно помнить, что подпись ссылки на функцию делегатом должна совпадать с подписью делегата как:

  1. // Delegate Definition  
  2. public delegate int operation(int x, int y);  

Обратите внимание на формат объявления типа делегата операции; он указывает, что объект операции может разрешить любой метод, принимающий два целых числа и возвращающий целое число. Если вы хотите вставить целевые методы в данный объект делегата, просто передайте имя метода конструктору делегата как: 

  1. // Delegate instantiation  
  2. operation obj = new operation(Program.Addition);  

На этом этапе вы можете вызвать член, на который указывает прямой вызов функции, как:

  1. Console.WriteLine("Addition is={0}",obj(23,27));   

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

Напомним, что делегаты .NET являются типобезопасными. Поэтому, если вы попытаетесь передать делегату метод, не соответствующий шаблону подписи, .NET сообщит об ошибке времени компиляции.
 

Массив делегатов


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

  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     public class Operation  
  6.     {  
  7.         public static void Add(int a, int b)  
  8.         {  
  9.             Console.WriteLine("Addition={0}",a + b);  
  10.         }  
  11.   
  12.         public static void Multiple(int a, int b)  
  13.         {  
  14.             Console.WriteLine("Multiply={0}", a * b);  
  15.         }  
  16.     }   
  17.   
  18.     class Program  
  19.     {  
  20.         delegate void DelOp(int x, int y);  
  21.   
  22.         static void Main(string[] args)  
  23.         {  
  24.             // Delegate instantiation  
  25.             DelOp[] obj =   
  26.            {  
  27.                new DelOp(Operation.Add),  
  28.                new DelOp(Operation.Multiple)  
  29.            };  
  30.    
  31.             for (int i = 0; i < obj.Length; i++)  
  32.             {  
  33.                 obj[i](2, 5);  
  34.                 obj[i](8, 5);  
  35.                 obj[i](4, 6);  
  36.             }  
  37.             Console.ReadLine();  
  38.         }  
  39.     }  
  40. }  

В этом коде вы создаете экземпляр массива делегатов Delop. Каждый элемент массива инициализируется для ссылки на другую операцию, реализованную классом операций. Затем вы просматриваете массив, применяя каждую операцию к трем различным значениям. После компиляции этого кода вывод будет следующим:

Array of Delegates
Figure 1.2


Анонимные методы


Анонимные методы, как следует из их названия, являются безымянными методами. Они предотвращают создание отдельных методов, особенно когда функциональность может быть реализована без создания нового метода. Анонимные методы обеспечивают более чистый и удобный подход при написании кода.

Определите анонимный метод с ключевым словом delegate и безымянным телом функции. Этот код назначает делегату анонимный метод. Анонимный метод не должен иметь подписи. Тип подписи и возвращаемого значения выводится из типа делегата. Например, если делегат имеет три параметра и возвращает тип double, то анонимный метод также будет иметь такую ​​же сигнатуру. 

  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     class Program  
  6.     {  
  7.         // Delegate Definition  
  8.         delegate void operation();  
  9.   
  10.         static void Main(string[] args)  
  11.         {  
  12.             // Delegate instantiation  
  13.             operation obj = delegate  
  14.             {  
  15.                 Console.WriteLine("Anonymous method");  
  16.             };  
  17.             obj();  
  18.    
  19.             Console.ReadLine();  
  20.         }  
  21.     }  
  22. }  

Анонимные методы уменьшают сложность кода, особенно если определено несколько событий. При анонимном методе код не работает быстрее. Компилятор по-прежнему неявно определяет методы.

 

Многоадресный делегат


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

Делегат многоадресной рассылки похож на виртуальный контейнер, в котором несколько функций ссылаются на сохраненный список вызовов. Он последовательно вызывает каждый метод в порядке FIFO (первым пришел, первым обслужен). Если вы хотите добавить несколько методов к объекту делегата, вы просто используете перегруженный оператор +=, а не прямое присваивание. 

  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     public class Operation  
  6.     {  
  7.         public static void Add(int a)  
  8.         {  
  9.             Console.WriteLine("Addition={0}", a + 10);  
  10.         }  
  11.         public static void Square(int a)  
  12.         {  
  13.             Console.WriteLine("Multiple={0}",a * a);  
  14.         }  
  15.     }  
  16.     class Program  
  17.     {  
  18.         delegate void DelOp(int x);  
  19.    
  20.         static void Main(string[] args)  
  21.         {  
  22.             // Delegate instantiation  
  23.             DelOp obj = Operation.Add;  
  24.             obj += Operation.Square;  
  25.    
  26.             obj(2);  
  27.             obj(8);  
  28.    
  29.             Console.ReadLine();  
  30.         }  
  31.     }  
  32. }  

Анонимные методы уменьшают сложность кода, особенно если определено несколько событий. При анонимном методе код не работает быстрее. Компилятор по-прежнему неявно определяет методы.

 

Многоадресный делегат


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

Делегат многоадресной рассылки похож на виртуальный контейнер, в котором несколько функций ссылаются на сохраненный список вызовов. Он последовательно вызывает каждый метод в порядке FIFO (первым пришел, первым обслужен). Если вы хотите добавить несколько методов к объекту делегата, вы просто используете перегруженный оператор +=, а не прямое присваивание. 

  1. // Delegate instantiation  
  2. DelOp obj = Operation.Add;  
  3. obj -= Operation.Square;  

Приведенный выше код объединяет два делегата, которые содержат функции Add() и Square(). Здесь сначала выполняется метод Add(), а затем Square(). Это порядок, в котором указатели функций добавляются к многоадресным делегатам.

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

  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     public class Operation  
  6.     {  
  7.         public static void One()  
  8.         {  
  9.             Console.WriteLine("one display");  
  10.             throw new Exception("Error");   
  11.         }  
  12.         public static void Two()  
  13.         {  
  14.             Console.WriteLine("Two display");  
  15.         }  
  16.     }  
  17.   
  18.     class Program  
  19.     {  
  20.         delegate void DelOp();  
  21.   
  22.         static void Main(string[] args)  
  23.         {  
  24.             // Delegate instantiation  
  25.             DelOp obj = Operation.One;  
  26.             obj += Operation.Two;  
  27.   
  28.             Delegate[] del = obj.GetInvocationList();  
  29.   
  30.             foreach (DelOp d in del)  
  31.             {  
  32.                 try  
  33.                 {  
  34.                     d();  
  35.                 }  
  36.                 catch (Exception)  
  37.                 {  
  38.                     Console.WriteLine("Error caught");  
  39.                 }  
  40.             }  
  41.             Console.ReadLine();  
  42.         }  
  43.     }  
  44. }  

Когда вы запустите приложение, вы увидите, что итерация по-прежнему продолжается со следующим методом даже после перехвата исключения, как показано ниже.

Multicast Delegate
Figure 1.3


События


Приложения и окна взаимодействуют с помощью предопределенных сообщений. Эти сообщения содержат различную информацию для определения действий как окна, так и приложения. .NET рассматривает эти сообщения как событие. Если вам нужно отреагировать на конкретное входящее сообщение, вы должны обработать соответствующее событие. Например, при нажатии кнопки в форме Windows отправляет сообщение WM_MOUSECLICK обработчику сообщения кнопки.

Вам не нужно создавать собственные методы для добавления или удаления методов в список вызовов делегатов. C# предоставляет ключевое слово event. Когда компилятор обрабатывает ключевое слово event, вы можете подписывать и отменять подписку на методы, а также любые необходимые переменные-члены для ваших типов делегатов. Синтаксис определения события должен быть следующим:

имя_делегата_события доступности имя_события;

Определение события — это двухэтапный процесс. Во-первых, вам нужно определить тип делегата, который будет содержать список методов, которые будут вызываться при запуске события. Затем вы объявляете событие, используя ключевое слово event. Чтобы проиллюстрировать событие, мы создаем консольное приложение. В этой итерации мы определим добавляемое событие, связанное с одним делегатом DelEventHandler. 

  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     public delegate void DelEventHandler();  
  6.   
  7.     class Program  
  8.     {  
  9.         public static event DelEventHandler add;  
  10.    
  11.         static void Main(string[] args)  
  12.         {  
  13.             add += new DelEventHandler(USA);  
  14.             add += new DelEventHandler(India);  
  15.             add += new DelEventHandler(England);  
  16.             add.Invoke();  
  17.    
  18.             Console.ReadLine();  
  19.         }  
  20.         static void USA()  
  21.         {  
  22.             Console.WriteLine("USA");    
  23.         }  
  24.    
  25.         static void India()  
  26.         {  
  27.             Console.WriteLine("India");  
  28.         }  
  29.    
  30.         static void England()  
  31.         {  
  32.             Console.WriteLine("England");  
  33.         }  
  34.     }  
  35. }  

В основном методе мы связываем событие с соответствующим обработчиком событий со ссылкой на функцию. Здесь мы также заполняем списки вызовов делегатов парой определенных методов, также используя оператор +=. Наконец, мы вызываем событие с помощью метода Invoke.


Примечание. Обработчики событий не могут возвращать значение. Они всегда пусты.

Давайте воспользуемся другим примером, чтобы лучше понять события. Здесь мы определяем имя события как xyz и делегат EventHandler со строковой подписью аргумента в классе операции. Мы помещаем реализацию в метод действия, чтобы подтвердить, запущено событие или нет.
  1. using System;  
  2.   
  3. namespace Delegates  
  4. {  
  5.     public delegate void EventHandler(string a);  
  6.   
  7.     public class Operation  
  8.     {  
  9.         public event EventHandler xyz;  
  10.   
  11.         public void Action(string a)  
  12.         {  
  13.             if (xyz != null)  
  14.             {  
  15.                 xyz(a);  
  16.                 Console.WriteLine(a);   
  17.             }  
  18.             else  
  19.             {  
  20.                 Console.WriteLine("Not Registered");   
  21.             }  
  22.         }  
  23.     }  
  24.    
  25.     class Program  
  26.     {  
  27.         public static void CatchEvent(string s)  
  28.         {  
  29.             Console.WriteLine("Method Calling");  
  30.         }  
  31.   
  32.         static void Main(string[] args)  
  33.         {  
  34.             Operation o = new Operation();  
  35.               
  36.             o.Action("Event Calling");   
  37.             o.xyz += new EventHandler(CatchEvent);  
  38.              
  39.             Console.ReadLine();  
  40.         }  
  41.     }  
  42. }  

Позже в классе Program мы определяем метод CatchEvent, на который будет ссылаться список вызовов делегатов. Наконец, в основном методе мы создали экземпляр класса операции и добавили реализацию запуска события.

Наконец, мы развиваем события на реалистичном примере создания пользовательского элемента управления «Кнопка» над формой Windows, который будет вызываться из консольного приложения. Во-первых, мы наследуем класс программы от класса Form и определяем связанное с ним пространство имен вверху. После этого в конструкторе класса программы мы создаем пользовательское управление кнопкой.

  1. using System;  
  2. using System.Drawing;  
  3. using System.Windows.Forms;  
  4.   
  5. namespace Delegates  
  6. {  
  7.     //custom delegate  
  8.     public delegate void DelEventHandler();  
  9.   
  10.     class Program :Form   
  11.     {  
  12.         //custom event  
  13.         public event DelEventHandler add;  
  14.   
  15.         public Program()  
  16.         {  
  17.             // desing a button over form  
  18.             Button btn = new Button();  
  19.             btn.Parent = this;  
  20.             btn.Text = "Hit Me";  
  21.             btn.Location = new Point(100,100);  
  22.   
  23.             //Event handler is assigned to  
  24.             // the button click event  
  25.             btn.Click += new EventHandler(onClcik);  
  26.             add += new DelEventHandler(Initiate);  
  27.   
  28.             //invoke the event  
  29.             add();  
  30.         }  
  31.         //call when event is fired  
  32.         public void Initiate()  
  33.         {  
  34.             Console.WriteLine("Event Initiated");  
  35.         }  
  36.    
  37.         //call when button clicked  
  38.         public void onClcik(object sender, EventArgs e)  
  39.         {  
  40.             MessageBox.Show("You clicked me");    
  41.         }  
  42.         static void Main(string[] args)  
  43.         {  
  44.             Application.Run(new Program());  
  45.    
  46.             Console.ReadLine();  
  47.         }  
  48.     }  
  49.   
  50. }  

Мы вызываем форму Windows, используя метод Run класса Application. Наконец, когда пользователь запускает это приложение, сначала на экране будет отображаться сообщение «Событие запущено» и форма Windows с пользовательским элементом управления «Кнопка». При нажатии кнопки появится окно сообщения, как показано ниже;

Figure4.jpg
Figure 1.4

Summary

Резюме

В этой статье мы узнали о делегатах и ​​событиях в C# и .NET. 

Комментариев нет:

Отправить комментарий

Паттерн 'Репозиторий' в ASP.NET

  Последнее обновление: 1.11.2015         Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозито...