Содержание
События в 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.