уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Итак, на данный момент мы научились объявлять в C# классы и задавать им различные свойства, а также  рассмотрели работы с таким интересным типом данных, как делегаты. Сегодня перейдем к не менее интересной теме — работе с событиями в C#.

Как обычно, начнем с  официальной информации Microsoft относительно заданной темы. Для чего нужны события?

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

Класс, отправляющий (или порождающий) событие, называется издателем, а классы, принимающие (или обрабатывающие) событие, называются подписчиками.

Ну, то есть суть использования событий в C# ничуть не отличается от использования событий в Delphi — сообщить о каких-либо изменениях другим объектам. Однако реализация механизма событий в C# сделана по-своему.

События в C#

Все события в библиотеке классов .NET Framework основаны на делегате EventHandler, который определен следующим образом:

public delegate void EventHandler(object sender, EventArgs e);
  • sender Object — это источник события.
  • e  EventArgs — объект, не содержащий данных события.

И, хотя события в определяемых классах могут быть основаны на любом допустимом типе делегата, даже на делегатах, возвращающих значение, обычно рекомендуется основывать события в .NET с помощью EventHandler.

Как видите, переменная e в делегате выше — это пустой объект, который не возвращает никаких данных о событии. Если представить описание этого делегата на языке Delphi (конечно же в контексте работы с событиями), то получится следующее:

TNotifyEvent = procedure(Sender: TObject) of object;

Если же наше событие должно передавать какие-либо параметры, то для соответствия рекомендациям Microsoft, мы должны передавать во втором параметре не EventArgs, а класс-наследник от EventArgs, содержащий необходимые данные.

На втором шаге создания событий для класса мы должны объявить делегат в классе-издателе и присвоить ему имя, которое заканчивается на EventHandler. То, что имя делегата должно завершаться на EventHandler — рекомендация Microsoft. Второй параметр делегата должен указывать на наследника от EventArgs.

На третьем шаге в классе-издателе определяется, собственно, само событие, которое в C# имеет следующее описание:

public event CustomEventHandler RaiseCustomEvent;

где event — ключевое слово, описывающее событие; CustomEventHandler — тип делегата (читай — его имя), который используется для события, а RaiseCustomEvent — имя нашего события.

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

Первое событие на C#

Итак, у нас уже есть класс, который пересчитывает координаты из метрической декартовой системы координат в систему WGS-84 и наоборот. Не трудно догадаться, что результат пересчёта при этом будет зависеть от того, какие базовые координаты в каждой системе координат будут заданы. Попробуем написать для этого класса событие, которое будет оповещать нас о том, что одна из базовых координат сменилась (X, Y, широта или долгота).

В Delphi для такой задачи мы бы сделали что-то наподобие этого:

type
  TMyCalss = class
  private
    FChangeEvent: TNotifyEvent;
    FValue: double;
    procedure SetValue(AValue: double);
  public
    property Value: double read FValue write SetValue;
    property ChangeEvent: TNotifyEvent read FChangeEvent write FChangeEvent;
  end;
 
implementation
 
procedure TMyCalss.SetValue(AValue: double);
begin
  FValue:=AValue;
  if Assigned(FChangeEvent) then
     FChangeEvent(self);
end;

То есть объявили бы событие типа TNotifyEvent и в сеттере SetValue это событие бы вызвали.

Теперь попробуем реализовать тоже самое, только с нашим классом Wgs84 в C#. Как мы уже знаем для публикации «пустых» событий в C# можно использовать делегат EventHandler . Так как делегат уже описан в System, то определять его заново не требуется (точно также, как в Delphi мы каждый раз не переопределяем TNotifyEvent), поэтому можем сразу перейти к описанию события. Также нам потребуется объект EventArgs с «пустыми данными», который мы можем создать в момент вызова события. Ок, создаем в нашем классе событие с именем Change:

public class Wgs84
{
.....
    //событие
    public event EventHandler Change;
.....
}

Теперь нам необходимо научиться вызывать это событие каждый раз при смене  одной из координат. Сделать это можно в  методе set соответствующего свойства класса. Например, вот так можно вызвать событие Change при  изменении широты:

public double BaseLatitude
        { get { return baseLatitude; }
 
            set {
                baseLatitude = value;
                bo = DegToRad(baseLatitude);
                w = Sqrt(1 - Pow(fe * Sin(bo), 2));
                //проверяем наличие подписчика
                if (Change != null)
                    Change(this, new EventArgs());//вызываем событие
            }
        }

Хорошо, вызывать (публиковать) событие мы научились. Теперь  посмотрим как такое событие отлавливать и обрабатывать. Мы знаем, что обрабатывать события должны подписчики (другие классы и объекты). Опять же, в Delphi мы бы обработали событие вот так:

TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    //обработчик события
    procedure OnChange(Sender: TObject);
  public
    { Public declarations }
  end;
 
implementation
 
procedure TForm17.FormCreate(Sender: TObject);
var MyClass: TMyClass;
begin
  MyClass:=TMyClass.Create;//создали объект
  MyClass.ChangeEvent:=OnChange;//"подписались" на событие
  MyClass.Value:=10; //меняем значение - сработает обработчик
end;
 
//обработчик события OnChange
procedure TForm17.OnChange(Sender: TObject);
begin
  ShowMessage('Что-то меняется');
end;

В принципе, аналогичная картина происходит и в C#. Для понимания приведу весь код программы:

using System;
using Geodesy;
 
namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            //создаем обработчик события
            void ChangeEvent(Object sender, EventArgs e)
            {
                Console.WriteLine("Координата сменилась");
            }
            //создаем объект типа Wgs84
            //при создании объекта событие не вызовется, так как обработчик ещё не назначен
            Wgs84 Coord = new Wgs84
            {
                BaseLatitude = 55.55,
                BaseLongitude = 74.75,
                X0 = 0,
                Y0 = 0,
 
            };
            //назначили обработчик события
            Coord.Change += ChangeEvent;
            //что-то делаем
            double B = 0;
            double L = 0;
            Coord.XY2BL(0, 0, ref B, ref L);  
            Console.WriteLine("Широта {0}; Долгота {1} ", B, L);
            Wgs84.DestinationPoint(55.55, 74.75, 90, 10000, ref B, ref L);
            Console.WriteLine("Конечная широта {0}; Конечная долгота {1} ", B, L);
            //меняем базовую координату - вот тут произойдет публикация события 
            Coord.BaseLatitude = 44.44;
            Console.ReadLine();
        }  
    }
}

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

Собственные события на C#

Усложним задачу и сделаем так, чтобы наше событие сообщало нам не только факт изменения базовых координат, но и к какой системе координат относятся изменения.

Для этого мы уже не сможем обойтись «пустым» EventArgs и создать для него потомка.

Более того, так как событие будет нашего собственного «производства», то придётся также определить тип делегата для нашего события. То есть задача увеличится на пару шагов. При создании события мы будем руководствоваться также рекомендациями Microsoft относительно работы с событиями.

Определяем потомка EventArgs. Для того, чтобы работа с пользовательскими данными события была доступна и для издателя и для подписчика, потомок EventArgs должен располагаться в области видимости обоих «заинтересованных сторон». Я решил его разместить в пространстве имен Geodesy. Выглядит потомок следующим образом:

public class ChangeEventArgs : EventArgs //наследуем от  EventArgs
    {
        //перечисление для определения типа системы координат
        public enum CoordKind { Metric, Gepgraphy};
        //авто определяемые поля класса
        public double NewValue { get; set; }
        public CoordKind Kind { get; set; }
 
        //конструктор класса
        public ChangeEventArgs(double newValue, CoordKind kind)
        {
            NewValue = newValue;
            Kind = kind;
        }
    }

Теперь необходимо объявить делегата и событие. Делегат, как и событие должны быть объявлены в классе-издателе:

public class Wgs84
{
//делегат для события
public delegate void ChangeEventHandler(Object sender, ChangeEventArgs args);
//само событие
public event ChangeEventHandler Change;
....
}

Остается разобраться с тем, как вызывать это событие. Покажу на примере свойств широты и X:

 public double BaseLatitude
        { get { return baseLatitude; }
            set {
                baseLatitude = value;
                bo = DegToRad(baseLatitude);
                w = Sqrt(1 - Pow(fe * Sin(bo), 2));
                if (Change != null)
                   //публикуем событие
                    Change(this, new ChangeEventArgs(value, ChangeEventArgs.CoordKind.Gepgraphy));
            }
        }
        protected double x0;
        public double X0 { get { return x0; }
            set
            {
                if (Change != null)
                   //публикуем событие
                    Change(this, new ChangeEventArgs(value, ChangeEventArgs.CoordKind.Metric));
            }
        }

Здесь мы публикуем наше событие и в нем создаем объект типа ChangeEventArgs в который передаем тип системы координат и новое значение изменяемой координаты. Обработать такое сообщение мы можем следующим образом:

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            //обработчик события соответствует делегату ChangeEventHandler
            void Change(Object sender, ChangeEventArgs e)
            {
                switch (e.Kind)
                {
                    case ChangeEventArgs.CoordKind.Gepgraphy:
                        Console.WriteLine("Изменилась географическая координата. Новое значение {0}", e.NewValue);
                        break;
                    case ChangeEventArgs.CoordKind.Metric:
                        Console.WriteLine("Изменилась метрическая координата. Новое значение {0}", e.NewValue);
                        break;
                };    
            }
            //создаем объект
            Wgs84 Coord = new Wgs84
            {
                BaseLatitude = 55.55,
                BaseLongitude = 74.75,
                X0 = 0,
                Y0 = 0,
 
            };
            //назначаем событие
            Coord.Change += Change;
            //изменяем свойства
            Coord.BaseLatitude = 44.44; //тут сработает событие
            Coord.X0 = 700; //тут сработает событие
            Console.ReadLine();
        }  
    }
}

Здесь мы в обработчике события используем данные, переданные нам событием во втором параметре и, используя условный оператор switch выводим на экран строку.

Упрощенный вызов делегатов

Пока писал пример, Visual Studio предложила мне упростить вызов делегата в методах set свойств, изменение которых генерирует события Change. Согласившись на изменение, получил вот такой упрощенный вызов делегата:

public double BaseLatitude
        { get { return baseLatitude; }
            set {
                //упрощенный вызов делегата
                Change?.Invoke(this, new ChangeEventArgs(value, ChangeEventArgs.CoordKind.Gepgraphy));
                /*
                раньше было так:
                if (Change != null)
                    Change(this, new ChangeEventArgs(value, ChangeEventArgs.CoordKind.Gepgraphy));  
 
                */
            }
        }

На работу программы это никак не повлияло, просто код программы стал на пару строчек короче. Какой из вариантов вызова делегатов выберите Вы — решать только Вам.

Итог

Итак, сегодня я постарался разобраться с тем, как работают события в C#, как их создавать с помощью базового типа делегата EventHandler и собственных делегатов, а также, с тем как передавать параметры сообщения в соответствии с рекомендациями Microsoft по публикации событий.

Книжная полка

Описание: практическое руководство познакомит вас с простыми рекомендациями, помогающими писать программное обеспечение, которое легко поддерживать и адаптировать. Написанная консультантами компании Software Improvement Group книга содержит ясные и краткие советы по применению рекомендаций на практике. Примеры для этого издания написаны на языке C#, но существует аналогичная книга с примерами на языке Java.
купить книгу delphi на ЛитРес
Описание: Книга представляет собой сборник советов, алгоритмов и готовых примеров программ на языке C# в среде MS Visual Studio 2005/2008 из различных областей: работа с формами и элементами управления, папками и файлами, мышью и клавиатурой, мультимедиа и графикой, использование технологий WMI и WSH, взаимодействие с MS Office и другими приложениями, работа в локальной сети и Интернете, особенности использования функций Windows API и др.
купить книгу delphi на ЛитРес
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
0 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии