четверг, 30 декабря 2021 г.

События

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

Например, возьмем следующий класс, который описывает банковский счет:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Account
{
    public Account(int sum)
    {
        Sum = sum;
    }
    // сумма на счете
    public int Sum { get; private set;}
    // добавление средств на счет
    public void Put(int sum)   
    {
        Sum += sum;
    }
    // списание средств со счета
    public void Take(int sum)
    {
        if (Sum >= sum)
        {
            Sum -= sum;
        }
    }
}


В конструкторе устанавливаем начальную сумму, которая хранится в свойстве Sum. С помощью метода Put мы можем добавить средства на счет, а с помощью метода Take, наоборот, снять деньги со счета. Попробуем использовать класс в программе - создать счет, положить и снять с него деньги:

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args)
{
    Account acc = new Account(100);
    acc.Put(20);    // добавляем на счет 20
    Console.WriteLine($"Сумма на счете: {acc.Sum}");
    acc.Take(70);   // пытаемся снять со счета 70
    Console.WriteLine($"Сумма на счете: {acc.Sum}");
    acc.Take(180);  // пытаемся снять со счета 180
    Console.WriteLine($"Сумма на счете: {acc.Sum}");
    Console.Read();
}


Все операции работают как и положено. Но что если мы хотим уведомлять пользователя о результатах его операций. Мы могли бы, например, для этого изменить метод Put следующим образом:

1
2
3
4
5
public void Put(int sum)   
{
    Sum += sum;
    Console.WriteLine($"На счет поступило: {sum}");
}

Казалось, теперь мы будем извещены об операции, увидев соответствующее сообщение на консоли. Но тут есть ряд замечаний. На момент определения класса мы можем точно не знать, какое действие мы хотим произвести в методе Put в ответ на добавление денег. Это может вывод на консоль, а может быть мы захотим уведомить пользователя по email или sms. Более того мы можем создать отдельную библиотеку классов, которая будет содержать этот класс, и добавлять ее в другие проекты. И уже из этих проектов решать, какое действие должно выполняться. Возможно, мы захотим использовать класс Account в графическом приложении и выводить при добавлении на счет в графическом сообщении, а не консоль. Или нашу библиотеку классов будет использовать другой разработчик, у которого свое мнение, что именно делать при добавлении на счет. И все эти вопросы мы можем решить, используя события.

Определение и вызов событий

События объявляются в классе с помощью ключевого слова event, после которого указывается тип делегата, который представляет событие:

1
2
delegate void AccountHandler(string message);
event AccountHandler Notify;

В данном случае вначале определяется делегат AccountHandler, который принимает один параметр типа string. Затем с помощью ключевого слова event определяется событие с именем Notify, которое представляет делегат AccountHandler. Название для события может быть произвольным, но в любом случае оно должно представлять некоторый делегат.

Определив событие, мы можем его вызвать в программе как метод, используя имя события:

1
Notify("Произошло действие");

Поскольку событие Notify представляет делегат AccountHandler, который принимает один параметр типа string - строку, то при вызове события нам надо передать в него строку.

Однако при вызове событий мы можем столкнуться с тем, что событие равно null в случае, если для его не определен обработчик. Поэтому при вызове события лучше его всегда проверять на null. Например, так:

1
if(Notify !=null) Notify("Произошло действие");

Или так:

1
Notify?.Invoke("Произошло действие");

В этом случае поскольку событие представляет делегат, то мы можем его вызвать с помощью метода Invoke(), передав в него необходимые значения для параметров.

Объединим все вместе и создадим и вызовем событие:

class Account
{
    public delegate void AccountHandler(string message);
    public event AccountHandler Notify;              // 1.Определение события
    public Account(int sum)
    {
        Sum = sum;
    }
    public int Sum { get; private set;}
    public void Put(int sum)   
    {
        Sum += sum;
        Notify?.Invoke($"На счет поступило: {sum}");   // 2.Вызов события
    }
    public void Take(int sum)
    {
        if (Sum >= sum)
        {
            Sum -= sum;
            Notify?.Invoke($"Со счета снято: {sum}");   // 2.Вызов события
        }
        else
        {
            Notify?.Invoke($"Недостаточно денег на счете. Текущий баланс: {Sum}"); ;
        }
    }
}


С событием может быть связан один или несколько обработчиков.

Обработчики событий - это именно то, что выполняется при вызове событий. 

Нередко в качестве обработчиков событий применяются методы. 

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

Для добавления обработчика события применяется операция +=:

1
Notify += обработчик события;

Определим обработчики для события Notify, чтобы получить в программе нужные уведомления:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Program
{
    static void Main(string[] args)
    {
        Account acc = new Account(100);
        acc.Notify += DisplayMessage;   // Добавляем обработчик для события Notify
        acc.Put(20);    // добавляем на счет 20
        Console.WriteLine($"Сумма на счете: {acc.Sum}");
        acc.Take(70);   // пытаемся снять со счета 70
        Console.WriteLine($"Сумма на счете: {acc.Sum}");
        acc.Take(180);  // пытаемся снять со счета 180
        Console.WriteLine($"Сумма на счете: {acc.Sum}");
        Console.Read();
    }
    private static void DisplayMessage(string message)
    {
        Console.WriteLine(message);
    }
}

В данном случае в качестве обработчика используется метод DisplayMessage, который соответствует по списку параметров и возвращаемому типу делегату AccountHandler. В итоге при вызове события Notify?.Invoke() будет вызываться метод DisplayMessage, которому для параметра message будет передаваться строка, которая передается в Notify?.Invoke(). В DisplayMessage просто выводим полученное от события сообщение, но можно было бы определить любую логику.

Если бы в данном случае обработчик не был бы установлен, то при вызове события Notify?.Invoke() ничего не происходило, так как событие Notify было бы равно null.

Консольный вывод программы:

На счет поступило: 20
Сумма на счете: 120
Со счета снято: 70
Сумма на счете: 50
Недостаточно денег на счете. Текущий баланс: 50
Сумма на счете: 50

Теперь мы можем выделить класс Account в отдельную библиотеку классов и добавлять в любой проект.


Добавление и удаление обработчиков

Для одного события можно установить несколько обработчиков и потом в любой момент времени их удалить. Для удаления обработчиков применяется операция -=. Например:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Program
{
    static void Main(string[] args)
    {
        Account acc = new Account(100);
        acc.Notify += DisplayMessage;       // добавляем обработчик DisplayMessage
        acc.Notify += DisplayRedMessage;    // добавляем обработчик DisplayMessage
        acc.Put(20);    // добавляем на счет 20
        acc.Notify -= DisplayRedMessage;     // удаляем обработчик DisplayRedMessage
        acc.Put(20);    // добавляем на счет 20
        Console.Read();
    }
 
    private static void DisplayMessage(string message)
    {
        Console.WriteLine(message);
    }
 
    private static void DisplayRedMessage(String message)
    {
        // Устанавливаем красный цвет символов
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(message);
        // Сбрасываем настройки цвета
        Console.ResetColor();
    }
}

Консольный вывод:

На счет поступило: 20
На счет поступило: 20
На счет поступило: 20

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    Account acc = new Account(100);
    // установка делегата, который указывает на метод DisplayMessage
    acc.Notify += new Account.AccountHandler(DisplayMessage);
    // установка в качестве обработчика метода DisplayMessage
    acc.Notify += DisplayMessage;       // добавляем обработчик DisplayMessage
     
    acc.Put(20);    // добавляем на счет 20
    Console.Read();
}
 
private static void DisplayMessage(string message)
{
    Console.WriteLine(message);
}

В данном случае разницы между двумя обработчиками никакой не будет.

Установка в качестве обработчика анонимного метода:

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args)
{
    Account acc = new Account(100);
    acc.Notify += delegate (string mes)
    {
        Console.WriteLine(mes);
    };
 
    acc.Put(20);
    Console.Read();
}

Установка в качестве обработчика лямбда-выражения:

1
2
3
4
5
6
7
8
static void Main(string[] args)
{
    Account acc = new Account(100);
    acc.Notify += mes =>Console.WriteLine(mes);
 
    acc.Put(20);
    Console.Read();
}

Управление обработчиками





Комментариев нет:

Отправить комментарий

Паттерн 'Репозиторий' в ASP.NET

  Последнее обновление: 1.11.2015         Одним из наиболее часто используемых паттернов при работе с данными является паттерн 'Репозито...