События в C# и их вызов

События в 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; }
}

Обратим внимание на следующие моменты:

  1. Мы объявили событие типа EventHandler с названием ChangeDepartment — оно будет порождаться, когда у объекта будет изменяться название отдела
  2. Свойства 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.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии