Содержание
События в C# позволяют классу или объекту уведомлять другие классы или объекты о возникновении каких-либо ситуаций. События активно используются в Windows-приложениях. Класс, который порождает (отправляет) событие, называется издателем, а классы, обрабатывающие (принимающие) событие, называются подписчиками. Соответственно, на одно и то же событие могут подписываться несколько подписчиков.
События в C#
Итак, вспомним наш класс Person, который мы использовали, когда разбирали тему использования делегатов. Итоговый класс у нас выглядел следующим образом:
public class Person { public delegate void PrintString(string str); //объявили делегат PrintString print; //объявили переменную делегата public string Name { get; set; } public string Surname { get; set; } public string Department { get; private set; } public byte Age { get; set; } public void ChangeDepartmnet(string newDepartment) { Department = newDepartment; print?.Invoke($"Отдел работы сотрудника {Name} {Surname} изменен на {newDepartment}"); } public void Register(PrintString printString) { print += printString; } public void Unregister(PrintString printString) { print -= printString; } }
Использование метода ChangeDepartmnet
в данном случае хоть и возможно, но избыточно. Более логично, в данном случае, позволить пользователю класса менять свойство Department
как и другие свойства и, при этом, в своей программе «отлавливать» момент изменения этого свойства и выводить результат в консоль. И здесь-то нам и помогут события в C#.
В C# событие определяется следующим образом:
event delegateType EventName;
здесь
event
— ключевое слово, которое сообщает нам, что перед нами событиеdelegateType
— это тип делегата. Делегат описывает то, как должен выглядеть метод в подписчике, который будет обрабатывать событие, а также то, какие параметры необходимо передавать подписчику. То есть, делегат, образно выражаясь, представляет из себя некий договор между издателем и подписчиком как они будут между собой общаться.EventName
— название события
В библиотеке классов .NET события основываются на делегате EventHandler
и базовом классе EventArgs
. Делегат EventHandler
выглядит следующим образом:
public delegate void EventHandler(object? sender, EventArgs e);
sender
— определяет класс или объект который породил событие (издатель)e
— класс, содержащий параметры, передаваемые подписчику.
Технически, зная то, что из себя представляет делегат в C# и как он определяется, мы можем определить делегат для нашего события в любом удобном для нас виде, хоть так:
public delegate void MyEventHandler(string str, DateTime date, out int count);
и затем объявить вот такое событие:
public event MyEventHandler MySuperEvent;
И наш код будет работать, НО правильным и хорошим тоном считается придерживаться следующего правила: делегат события содержит два параметра — первый предоставляет подписчику информацию об издателе, а второй — наследник класса EventArgs
передает подписчику необходимые параметры. Попробуем переписать наш класс Person и создать свое первое событие в C#
Создание события в C#
Перепишем наш класс Person следующим образом:
public class Person { //событие при смене отдела public event EventHandler ChangeDepartment; public string Name { get; set; } public string Surname { get; set; } private string department; public string Department { get { return department; } set { if (department != value) { department = value; ChangeDepartment?.Invoke(this, new EventArgs()); } } } public byte Age { get; set; } }
Обратим внимание на следующие моменты:
- Мы объявили событие типа
EventHandler
с названиемChangeDepartment
— оно будет порождаться, когда у объекта будет изменяться название отдела - Свойства
Department
вместо сокращенной формы записи теперь имеет блокиget
иset
. При этом, в блоке set проверяется действительно ли новое название отдела не совпадает с текущим и только, если новое название не совпадает со старым меняется значение уdepartment
и вызывается метод делегата.
Теперь мы можем подписаться на это событие и получать уведомления от издателя. Сделать это можно, например, так:
class Program { static void Main(string[] args) { Person person = new Person { Name = "Вася", Surname = "Пупкин", Department = "Курьерский" }; person.ChangeDepartment += EventHandler; person.Department = "Общий"; //здесь событие сработает person.Department = "Общий"; //здесь событие не сработает } public static void EventHandler(object sender, EventArgs e) { Console.WriteLine($"У работника сменился отдел на {((Person)sender).Department}"); } }
Здесь мы создали объект класс Person
, и назначили в качестве обработчика события ChangeDepartment
метод EventHandler
. Теперь, если запустить приложение, то можно увидеть, что событие сработает ровно один раз:
Подписка на события в C#
На любое событие в C# может быть подписано любое количество подписчиков. Более того, один и тот же класс может подписаться любое количество раз на событие:
class Program { static void Main(string[] args) { Person person = new Person { Name = "Вася", Surname = "Пупкин", Department = "Курьерский" }; person.ChangeDepartment += EventHandler; person.ChangeDepartment += EventHandler_2; person.Department = "Общий"; //здесь событие сработает } public static void EventHandler(object sender, EventArgs e) { Console.WriteLine($"У работника сменился отдел на {((Person)sender).Department}"); } public static void EventHandler_2(object sender, EventArgs e) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"У работника сменился отдел на {((Person)sender).Department}"); Console.ResetColor(); } }
Как и в случае работы с обычными делегатами, методы EventHandler
и EventHandler_2
будут вызваны последовательно:
У работника сменился отдел на Общий
Создание собственных делегатов событий в C#
В приведенном выше примере мы использовали уже имеющийся в .NET делегат событияEventHandler
, который возвращал нам, опять же, имеющийся в .NET C# класс EventArgs
. По сути EventArgs
— это пустой набор параметров и выглядит он следующим образом:
public class EventArgs { public static readonly EventArgs Empty; public EventArgs() { } }
Но что, если нам потребуется, чтобы событие ChangeDepartment
сообщало нам не только факт смены отдела, но и то, какой отдел был ранее? Здесь нам поможет создание собственного делегата события. Делается это следующим образом:
Во-первых, создаем класс-наследник EventArgs
который будет содержать необходимые параметры. Например, можем определить такой класс:
public class ChangeDepartmentArgs : EventArgs { public string OldDepartment { get; set; } public string NewDepartment { get; set; } public ChangeDepartmentArgs(string oldDepartment, string newDepartment) { OldDepartment = oldDepartment; NewDepartment = newDepartment; } }
здесь стоит обратить внимание на конструктор — в нем мы как раз передаем старое и новое название и присваиваем эти значения свойствам класса OldDepartment
и NewDepartment
.
Во-вторых, объявляем делегат для события:
public delegate void ChangeDepartmentHandler(object sender, ChangeDepartmentArgs e);
В третьих, определяем событие в классе:
public event ChangeDepartmentHandler ChangeDepartment;
В-четвертых, обеспечить внутри класса вызов события:
public class Person { прочие свойства класса private string department; public string Department { get { return department; } set { if (department != value) { string old = department; department = value; ChangeDepartment?.Invoke(this, new ChangeDepartmentArgs(old, department)); } } } }
Нам остается только подписаться на событие и получать сообщения от издателя. Ниже показан код основной программы:
class Program { static void Main(string[] args) { Person person = new Person { Name = "Вася", Surname = "Пупкин", Department = "Курьерский" }; person.ChangeDepartment += EventHandler; person.Department = "Общий"; //здесь событие сработает } public static void EventHandler(object sender, ChangeDepartmentArgs e) { Console.WriteLine($"У работника сменился отдел с {e.OldDepartment} на {e.NewDepartment}"); } }
Теперь у нас класс Person
содержит собственное событие, используя которое мы можем получать информацию об изменении свойства Department
, включая и предыдущее. название. В C# мы можем создавать события, которые могут отдавать (и получать) любое количество параметров.
Итого
Сегодня мы рассмотрели основные моменты по созданию событий у классов в C#, научились определять события в классе, подписываться на события и создавать свои собственные события в C#. В библиотеке классов .NET события основываются на делегате EventHandler
и базовом классе EventArgs
.