1. Темы лекции: Делегаты, события, анонимные методы.
Практическое задание: Делегаты, события, анонимные методы.
Тренер: Игорь Шкулипа, к.т.н.
Платформа .Net и язык программирования C#.
Занятие 6
2. http://www.slideshare.net/IgorShkulipa 2
Делегаты
Делегаты являются ссылками на методы, инкапсулирующими настоящие
указатели и предоставляющими удобные сервисы для работы с ними.
Ссылки представляют собой объекты соответствующего типа. Все
делегаты являются объектами типа System.Delegate или
System.MulticastDelegate, который является наследником первого.
[SerializableAttribute]
[ClassInterfaceAttribute(ClassInterfaceType.AutoDual)]
[ComVisibleAttribute(true)]
public abstract class Delegate : ICloneable, ISerializable
[SerializableAttribute]
[ComVisibleAttribute(true)]
public abstract class MulticastDelegate : Delegate
Делегаты обеспечивают, поддерживаемый языком, механизм определения
и выполнения обратных вызовов.
В С++ есть аналог делегатов – это указатели на функции.
3. http://www.slideshare.net/IgorShkulipa 3
Создание и использование делегатов
public delegate double SomeDelegate(double x, double y);
Встречая такую конструкцию, компилятор автоматически генерирует
класс-наследник MulticastDelegate, автоматически реализуя все
необходимые методы. В частности, метод Invoke(), который, по сути, и
является вызовом делегата.
К делегатам можно привязывать как статичные методы класса, так и
методы конкретного экземпляра.
На практике, вызов делегата подобен вызову обычной функции.
public delegate Complex ComplAction(Complex c);
static void Main(string[] args)
{
Complex cc1 = new Complex(1, 2);
Complex cc2 = new Complex(2, 3);
ComplAction[] ca = new ComplAction[2];
ca[0] = cc1.Add;
ca[1] = cc1.Sub;
Console.WriteLine(ca[0](cc2).ToString());
Console.WriteLine(ca[1](cc2).ToString());
}
4. http://www.slideshare.net/IgorShkulipa 4
Класс Complex и объявление делегата
public class Complex : IComparable
{
public double Re { get; set; }
public double Im { get; set; }
// ... Конструкторы
public Complex Add(Complex right)
{
return new Complex(this.Re + right.Re, this.Im + right.Im);
}
public Complex Sub(Complex right)
{
return new Complex(this.Re - right.Re, this.Im - right.Im);
}
public void Print()
{
Console.WriteLine(this.ToString());
}
// Полное описание класса см. Презентацию № 3.
}
5. http://www.slideshare.net/IgorShkulipa 5
Цепочки делегатов
Цепочка делегатов позволяет создавать связный список делегатов так, чтобы, при
вызове первого делегата из списка, вызывались все остальные последовательно.
Для управления цепочками делегатов, класс System.Delegate предоставляет
следующие методы:
1. Для группирования делегатов в цепочки используется метод Combine()
public class Delegate: ICloneable, ISerializable
{
public static Delegate Combine(Delegate[] );
public static Delegate Combine(Delegate first, Delegate second);
}
2. Для удаления делегата из списка служат методы Remove и RemoveAll
public class Delegate: ICloneable, ISerializable
{
public static Delegate Remove(Delegate source, Delegate value );
public static Delegate RemoveAll(Delegate source, Delegate value );
}
Так же, для цепочек делегатов перегружены операции сложения (+) и вычитания (-),
обеспечивающие добавление и удаление делегата из списка.
6. http://www.slideshare.net/IgorShkulipa 6
Использование цепочек делегатов
public delegate void ComplPrint();
class Program
{
static void Main(string[] args)
{
Complex cc1 = new Complex(1, 2);
Complex cc2 = new Complex(2, 3);
Complex cc3 = new Complex(3, 4);
ComplPrint[] cp = new ComplPrint[3];
cp[0] = cc1.Print;
cp[1] = cc2.Print;
cp[2] = cc3.Print;
ComplPrint chainedDelegates = cp[0] + cp[1] + cp[2];
chainedDelegates();
Console.ReadKey();
}
}
}
1+i2
2+i3
3+i4
7. http://www.slideshare.net/IgorShkulipa 7
Итерация по цепочкам делегатов
Если требуется вызвать конкретный делегат из цепочки, или вызвать все делегаты в
определенной последовательности, то для этого, класс Delegate предоставляет
метод GetInvocationList(), который возвращает массив делегатов из цепочки.
public delegate void ComplPrint();
class Program {
static void Main(string[] args)
{
Complex cc1 = new Complex(1, 2);
Complex cc2 = new Complex(2, 3);
Complex cc3 = new Complex(3, 4);
ComplPrint[] cp = new ComplPrint[3];
cp[0] = cc1.Print;
cp[1] = cc2.Print;
cp[2] = cc3.Print;
ComplPrint chainedDelegates = cp[0] + cp[1] + cp[2];
Delegate[] delegateList = chainedDelegates.GetInvocationList();
for (int i = delegateList.Length - 1; i >= 0; i--) {
((ComplPrint)delegateList[i])();
}
Console.ReadKey();
}
}
3+i4
2+i3
1+i2
8. http://www.slideshare.net/IgorShkulipa 8
Делегаты открытого экземпляра
Делегаты открытого экземпляра (несвязанные делегаты) используются, когда необходимо,
чтобы делегат представлял метод определенного экземпляра, но вызывать этот метод
можно на целой коллекции экземпляров.
Класс MethodInfo выявляет атрибуты метода и обеспечивает доступ к его метаданным.
using System.Reflection;
public delegate void PrintAllComplex(Complex c);
class Program {
static void Main(string[] args) {
Complex cc1 = new Complex(1, 2); Complex cc2 = new Complex(2, 3);
Complex cc3 = new Complex(3, 4);
List<Complex> cl = new List<Complex>();
cl.Add(cc1); cl.Add(cc2); cl.Add(cc3);
// Создаем делегат открытого эезкмпляра
MethodInfo mi =
typeof(Complex).GetMethod("Print",
BindingFlags.Public | BindingFlags.Instance);
PrintAllComplex openDelegate =
(PrintAllComplex)Delegate.CreateDelegate(typeof(PrintAllComplex), mi);
foreach (var c in cl) {
openDelegate(c);
}
Console.ReadKey();
}
}
1+i2
2+i3
3+i4
12. http://www.slideshare.net/IgorShkulipa 12
Использование моста
class Program
{
static void Main(string[] args)
{
Abstraction abstr = new Abstraction();
abstr.SetImplementor(new Implementor1());
abstr.Operation();
abstr.SetImplementor(new Implementor2());
abstr.Operation();
Console.ReadKey();
}
}
Результат:
Implementor 1
Implementor 2
13. http://www.slideshare.net/IgorShkulipa 13
События
События позволяют классу или объекту уведомлять другие классы или
объекты о возникновении каких-либо ситуаций. Класс, отправляющий
(или вызывающий) событие, называется издателем, а классы,
принимающие (или обрабатывающие) событие, называются
подписчиками.
В C# в приложении Windows Forms или веб-приложении пользователь
подписывается на события, вызываемые элементами управления,
такими как кнопки и поля со списками.
С точки зрения синтаксиса объявления, события – это, по сути,
сокращение, которое избавляет от необходимости ручного создания
цепочек делегатов и регистрации делегатов в цепочках.
Делегат – указатель на метод.
Событие – указатель на несколько методов.
14. http://www.slideshare.net/IgorShkulipa 14
Свойства событий
События имеют следующие свойства:
• Издатель определяет момент вызова события, подписчики
определяют предпринятое ответное действие.
• У события может быть несколько подписчиков. Подписчик
может обрабатывать несколько событий от нескольких издателей.
• События, не имеющие подписчиков, никогда не возникают.
• Обычно события используются для оповещения о действиях
пользователя, таких как нажатия кнопок или выбор меню и их
пунктов в графическом пользовательском интерфейсе.
• Если событие имеет несколько подписчиков, то при его
возникновении происходит синхронный вызов обработчиков
событий.
• В библиотеке классов .NET Framework в основе событий лежит
делегат EventHandler и базовый класс EventArgs.
15. http://www.slideshare.net/IgorShkulipa 15
Пример: класс аргументов события
public class OnPowerOnArgs : EventArgs
{
public string DisplayText
{
get;
private set;
}
public OnPowerOnArgs(string strText)
{
DisplayText = strText;
}
}
16. http://www.slideshare.net/IgorShkulipa 16
Класс-издатель события
public class UserInterface
{
public event EventHandler<OnPowerOnArgs> OnPowerOn;
public UserInterface()
{
OnPowerOn += HandlePowerOn;
}
public void InitiatePowerOn(object sender, OnPowerOnArgs args)
{
OnPowerOn.Invoke(sender, args);
}
protected void HandlePowerOn(object sender, OnPowerOnArgs args)
{
Console.WriteLine(sender.ToString()+": "+args.DisplayText);
}
}
18. http://www.slideshare.net/IgorShkulipa 18
Паттерн Chain of Responsibility
Паттерн Chain of Responsibility позволяет избежать жесткой
зависимости отправителя запроса от его получателя, при этом
запрос может быть обработан несколькими объектами.
Объекты-получатели связываются в цепочку. Запрос
передается по этой цепочке, пока не будет обработан.
Вводит конвейерную обработку для запроса с множеством
возможных обработчиков.
Объектно-ориентированный связанный список с рекурсивным
обходом.
20. http://www.slideshare.net/IgorShkulipa 20
Реализация цепочки. Классы событий
public abstract class IEvent{
public string EventType { get; set; }
}
class Event1 : IEvent {
public Event1() { EventType = "Event1"; }
}
class Event2 : IEvent {
public Event2() { EventType = "Event2"; }
}
class Event3 : IEvent {
public Event3() { EventType = "Event3"; }
}
class Event4 : IEvent {
public Event4() { EventType = "Event4"; }
}
class Event5 : IEvent {
public Event5() { EventType = "Event5"; }
}
class Event6 : IEvent {
public Event6() { EventType = "Event6"; }
}
21. http://www.slideshare.net/IgorShkulipa 21
Базовый класс-обработчик
public abstract class BaseHandler {
public BaseHandler() { Next = null; }
public virtual void Handle(IEvent ev) {
if (PrivateEvent.EventType == ev.EventType)
{
Console.WriteLine("{0} successfully handled", PrivateEvent.EventType);
} else {
Console.WriteLine("Sending event to next Handler...");
if (Next != null)
Next.Handle(ev);
else
Console.WriteLine("Unknown event. Can't handle.");
}
}
protected void SetNextHandler(BaseHandler newHandler) {
Next = newHandler;
}
protected BaseHandler Next { get; set; }
protected IEvent PrivateEvent { get; set; }
}
22. http://www.slideshare.net/IgorShkulipa 22
Классы-обработчики
class Handler5 : BaseHandler {
public Handler5() {
PrivateEvent = new Event5(); Next = null;
} }
class Handler4 : BaseHandler {
public Handler4() {
PrivateEvent = new Event4(); Next = new Handler5();
} }
class Handler3 : BaseHandler {
public Handler3() {
PrivateEvent = new Event3(); Next = new Handler4();
} }
class Handler2 : BaseHandler {
public Handler2() {
PrivateEvent = new Event2(); Next = new Handler3();
} }
class Handler1 : BaseHandler {
public Handler1() {
PrivateEvent = new Event1(); Next = new Handler2();
} }
23. http://www.slideshare.net/IgorShkulipa 23
Класс тестового приложения
public class ChainApplication {
public ChainApplication() {
eventHandler = new Handler1(); Rand = new Random();
}
public void Run(int EventCount) {
for (int i = 0; i < EventCount; i++) {
HandleEvent(GenerateRandomEvent());
} }
private void HandleEvent(IEvent ev) {
eventHandler.Handle(ev);
}
private IEvent GenerateRandomEvent() {
IEvent result;
switch (Rand.Next(1,6)) {
case 0: result = new Event1(); break; case 1: result = new Event2(); break;
case 2: result = new Event3(); break; case 3: result = new Event4(); break;
case 4: result = new Event5(); break; default: result = new Event6(); break; }
Console.WriteLine("Generated event: {0}", result.EventType);
return result; }
private BaseHandler eventHandler;
private Random Rand;
}
24. http://www.slideshare.net/IgorShkulipa 24
Результат цепочки ответственностей
class Program
{
static void Main(string[] args)
{
ChainApplication app = new ChainApplication();
app.Run(3);
Console.ReadKey();
}
}
Результат:
Generated event: Event4
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Event4 successfully handled
Generated event: Event6
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Unknown event. Can't handle.
Generated event: Event5
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Sending event to next Handler...
Event5 successfully handled
25. http://www.slideshare.net/IgorShkulipa 25
Паттерн «Стратегия»
Паттерн Стратегия (Strategy) предназначен для определения
семейства алгоритмов и инкапсуляции каждого из них и
обеспечения их взаимозаменяемости.
Переносит в отдельную иерархию классов все детали, связанные
с реализацией алгоритмов.
27. http://www.slideshare.net/IgorShkulipa 27
Классы конкретных стратегий
class GoToWorkStrategy : Istrategy {
public override void Use()
{
WakeUp(); Shower(); Dress(); GoOut();
GoToBusStop(); Wait(); Arrive(); DoWork();
}
}
class GoWalkStrategy : Istrategy {
public override void Use()
{
GoOut(); GoToPark(); Walk();
}
}
class GoToGymStrategy : Istrategy {
public override void Use()
{
GoOut(); GoToBusStop(); Arrive(); DoExercises();
}
}
28. http://www.slideshare.net/IgorShkulipa 28
Клиент стратегий
public abstract class IStrategyClient
{
public abstract void UseStrategy();
public void SetStrategy(IStrategy st) { strategy = st; }
protected IStrategy strategy;
}
class StrategyClient1 : IStrategyClient
{
public StrategyClient1() { }
public override void UseStrategy()
{
strategy.Use();
}
}
29. http://www.slideshare.net/IgorShkulipa 29
Использование стратегий
class Program
{
static void Main(string[] args)
{
IStrategyClient stClient = new StrategyClient1();
stClient.SetStrategy(new GoToWorkStrategy());
stClient.UseStrategy();
Console.WriteLine();
stClient.SetStrategy(new GoToGymStrategy());
stClient.UseStrategy();
Console.WriteLine();
stClient.SetStrategy(new GoWalkStrategy());
stClient.UseStrategy();
Console.ReadKey();
}
}
Wake up.
Take shower.
Dress.
Go out.
Go to bus stop.
Wait.
Arrive.
Do work.
Go out.
Go to bus stop.
Arrive.
Do exercises.
Go out.
Go to park.
Walk.
30. http://www.slideshare.net/IgorShkulipa 30
Анонимные методы
Новый класс стратегий:
public delegate void StrategyDelegate();
public abstract class IStrategyClient
{
public abstract void UseStrategy();
public StrategyDelegate Strategy { get; set; }
}
class StrategyClient1 : IStrategyClient
{
public StrategyClient1() { }
public override void UseStrategy()
{
Strategy();
}
}
31. http://www.slideshare.net/IgorShkulipa 31
Использование анонимных методов
class Program
{
static void Main(string[] args)
{
IStrategyClient stClient = new StrategyClient1();
IStrategy goWork = new GoToWorkStrategy();
IStrategy goGym = new GoToGymStrategy();
IStrategy goWalk = new GoWalkStrategy();
stClient.Strategy = delegate {
Console.WriteLine("Anonymous Method:");
goWork.Use();
Console.WriteLine(); };
stClient.UseStrategy();
stClient.Strategy = delegate {
Console.WriteLine("Anonymous Method:");
goGym.Use(); Console.WriteLine(); };
stClient.UseStrategy();
stClient.Strategy = delegate {
Console.WriteLine("Anonymous Method:");
goWalk.Use(); Console.WriteLine(); };
stClient.UseStrategy();
Console.ReadKey();
}
}
Anonymous Method:
Wake up.
Take shower.
Dress.
Go out.
Go to bus stop.
Wait.
Arrive.
Do work.
Anonymous Method:
Go out.
Go to bus stop.
Arrive.
Do exercises.
Anonymous Method:
Go out.
Go to park.
Walk.
32. http://www.slideshare.net/IgorShkulipa 32
Расширяющие методы
Расширяющие методы (методы расширения) позволяют "добавлять" методы в
существующие типы без создания нового производного типа, перекомпиляции или
иного изменения исходного типа.
Расширяющие методы являются особым видом статического метода, но они
вызываются, как если бы они были методами экземпляра в расширенном типе. Для
клиентского кода, написанного на языках C#, нет видимого различия между
вызовом метода расширения и вызовом методов, фактически определенных в типе.
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Методы расширения определяются как статические методы, но вызываются с
помощью синтаксиса обращения к методу экземпляра. Их первый параметр
определяет, с каким типом оперирует метод, и перед параметром идет
модификатор this. Методы расширения находятся в области действия, только если
пространство имен было явно импортировано в исходный код с помощью
директивы using.
33. http://www.slideshare.net/IgorShkulipa 33
Пример расширяющих методов
public static class ExtensionMethods
{
public static double Angle(this Complex compl)
{
if ((compl.Re == 0) && (compl.Im >= 0)) {
return Math.PI / 2;
}
if ((compl.Re == 0) && (compl.Im < 0)) {
return 3 * Math.PI / 2;
}
return Math.Atan(compl.Im / compl.Re);
}
}
class Program
{
static void Main(string[] args)
{
Complex c1 = new Complex(1,2);
Console.WriteLine(c1);
double angle = c1.Angle();
Console.WriteLine(angle);
Console.ReadKey();
}
}
34. http://www.slideshare.net/IgorShkulipa 34
Шаблон проектирования «Visitor»
Паттерн Visitor определяет операцию, выполняемую на каждом
элементе из некоторой структуры без изменения классов этих
объектов.
• Паттерн Visitor определяет операцию, выполняемую на каждом
элементе из некоторой структуры. Позволяет, не изменяя классы
этих объектов, добавлять в них новые операции.
Применение расширяющих методов значительно упрощает реализацию
этого паттерна.
35. http://www.slideshare.net/IgorShkulipa 35
Классы «компонентов»
public class SomeClass1
{
public SomeClass1(int c) { SomeProperty1 = c; }
public int SomeProperty1 { get; set; }
}
public class SomeClass2
{
public SomeClass2(int c) { SomeProperty2 = c; }
public int SomeProperty2 { get; set; }
}
public class SomeClass3
{
public SomeClass3(int c) { SomeProperty3 = c; }
public int SomeProperty3 { get; set; }
}
36. http://www.slideshare.net/IgorShkulipa 36
Класс-посетитель
public static class Visitor
{
public static void Visit(this SomeClass1 sc1)
{
Console.WriteLine(sc1.SomeProperty1);
}
public static void Visit(this SomeClass2 sc2)
{
Console.WriteLine(sc2.SomeProperty2);
}
public static void Visit(this SomeClass3 sc3)
{
Console.WriteLine(sc3.SomeProperty3);
}
}
38. http://www.slideshare.net/IgorShkulipa 38
Лабораторная работа № 6. Делегаты, события
Используя класс «Квадратная матрица» из лабораторной работы №3,
реализовать тестовое приложение «Матричный калькулятор».
Добавить к классу расширяющие методы транспонирования матрицы и
нахождения следа матрицы (сумма диагональных элементов).
Создать делегат на основе анонимного метода приведения матрицы к
диагональному виду.
Реализовать меню для управления вычислений на основе делегатов с
использованием паттерна проектирования «Цепочка ответственности».