Object sender c что это
Перейти к содержимому

Object sender c что это

  • автор:

Object sender c что это

Для взаимодействия с пользователем в Windows Forms используется механизм событий. События в Windows Forms представляют стандартные события на C#, только применяемые к визуальным компонентам и подчиняются тем же правилам, что события в C#. Но создание обработчиков событий в Windows Forms все же имеет некоторые особенности.

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

События в Windows Forms

Чтобы добавить обработчик, можно просто два раза нажать по пустому полю рядом с названием события, и после этого Visual Studio автоматически сгенерирует обработчик события. Например, нажмем для создания обработчика для события Load :

И в этом поле отобразится название метода обработчика события Load. По умолчанию он называется Form1_Load .

Если мы перейдем в файл кода формы Form1.cs, то увидим автосгенерированный метод Form1_Load:

public partial class Form1 : Form < public Form1() < InitializeComponent(); >private void Form1_Load(object sender, EventArgs e) < >>

И при каждой загрузке формы будет срабатывать код в обработчике Form1_Load.

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

Но это только обработчик. Добавление же обработчика, созданного таким образом, производится в файле Form1.Designer.cs:

namespace HelloApp < partial class Form1 < private System.ComponentModel.IContainer components = null; protected override void Dispose(bool disposing) < if (disposing && (components != null)) < components.Dispose(); >base.Dispose(disposing); > private void InitializeComponent() < this.SuspendLayout(); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(284, 261); this.Name = "Form1"; // добавление обработчика this.Load += new System.EventHandler(this.Form1_Load); this.ResumeLayout(false); >> >

Для добавления обработчика используется стандартный синтаксис C#: this.Load += new System.EventHandler(this.Form1_Load)

Поэтому если мы захотим удалить созданный подобным образом обработчик, то нам надо не только удалить метод из кода формы в Form1.cs, но и удалить добавление обработчика в этом файле.

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

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace HelloApp < public partial class Form1 : Form < public Form1() < InitializeComponent(); this.Load += LoadEvent; >private void Form1_Load(object sender, EventArgs e) < >private void LoadEvent(object sender, EventArgs e) < this.BackColor = Color.Yellow; >> >

Кроме ранее созданного обработчика Form1_Load здесь также добавлен другой обработчик загрузки формы: this.Load += LoadEvent; , который устанавливает в качестве фона желтый цвет.

События .NET в деталях

Если вы .NET программист, то вы наверняка объявляли и использовали события в своем коде. Несмотря на это, не все знают, как события работают внутри и какие особенности связаны с их применением. В этой статье я попытался описать работу событий как можно более подробно, включая некоторые частные случаи, с которыми редко приходится иметь дело, но про которые важно и\или интересно знать.

Что такое событие?

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

public event EventHandler Changed; 

Рассмотрим, из чего состоит объявление. Сначала идут модификаторы события, затем ключевое слово event, после него — тип события, который обязательно должен быть типом-делегатом, и идентификатор события, то есть его имя. Ключевое слово event сообщает компилятору о том, что это не публичное поле, а специальным образом раскрывающаяся конструкция, скрывающая от программиста детали реализации механизма событий. Для того, чтобы понять, как работает этот механизм, необходимо изучить принципы работы делегатов.

Основа работы событий — делегаты

Можно сказать, что делегат в .NET — некий аналог ссылки на функцию в C++. Вместе с тем, такое определение неточно, т.к. каждый делегат может ссылаться не на один, а на произвольное количество методов, которые хранятся в списке вызовов делегата (invocation list). Тип делегата описывает сигнатуру метода, на который он может ссылаться, экземпляры этого типа имеют свои методы, свойства и операторы. При вызове метода Invoke() выполняется последовательный вызов каждого из методов списка. Делегат можно вызывать как функцию, компилятор транслирует такой вызов в вызов Invoke().
В C# для делегатов имеются операторы + и -, которые не существуют в среде .NET и являются синтаксическим сахаром языка, раскрываясь в вызов методов Delegate.Combine и Delegate.Remove соответственно. Эти методы позволяют добавлять и удалять методы в списке вызовов. Разумеется, форма операторов с присваиванием (+= и -=) также применима к операторам делегата, как и к определенным в среде .NET операторам + и — для других типов. Если при вычитании из делегата его список вызовов оказывается пуст, то ему присваивается null.
Рассмотрим простой пример:

Action a = () => Console.Write("A"); //Action объявлен как public delegate void Action(); Action b = a; Action c = a + b; Action d = a - b; a(); //выведет A b(); //выведет A c(); //выведет AA d(); //произойдет исключение NullReferenceException, т.к. d == null 
События — реализация по умолчанию
  1. Неявная реализация события (field-like event).
  2. Явная реализация события.

Рассмотрим наиболее часто используемую реализацию событий — неявную. Пусть имеется следующий исходный код на языке C# 4 (это важно, для более ранних версий генерируется несколько иной код, о чем будет рассказано далее):

class Class

Эти строчки будут транслированы компилятором в код, аналогичный следующему:

class Class < EventHandler сhanged; public event EventHandler Changed < add < EventHandler eventHandler = this.changed; EventHandler comparand; do < comparand = eventHandler; eventHandler = Interlocked.CompareExchange(ref this.changed, comparand + value, comparand); > while(eventHandler != comparand); > remove < EventHandler eventHandler = this.changed; EventHandler comparand; do < comparand = eventHandler; eventHandler = Interlocked.CompareExchange(ref this.changed, comparand - value, comparand); > while (eventHandler != comparand); > > >

Блок add вызывается при подписке на событие, блок remove — при отписке. Эти блоки компилируются в отдельные методы с уникальными именами. Оба этих метода принимают один параметр — делегат типа, соответствующего типу события и не имеют возвращаемого значения. Имя параметра всегда ”value”, попытка объявить локальную переменную с таким именем приведет к ошибке компиляции. Область видимости, указанная слева от ключевого слова event определяет область видимости этих методов. Также создается делегат с именем события, который всегда приватный. Именно поэтому мы не можем вызвать событие, реализованное неявным способом, из наследника класса.

Interlocked.CompareExchange выполняет сравнение первого аргумента с третьим и если они равны, заменяет первый аргумент на второй. Это действие потокобезопасно. Цикл используется для случая, когда после присвоения переменной comparand делегата события и до выполнения сравнения другой поток изменяет этот делегат. В таком случае Interlocked.CompareExchange не производит замены, граничное условие цикла не выполняется и происходит следующая попытка.

Объявление с указанием add и remove

При явной реализации события программист объявляет делегат-поле для события и вручную добавляет или удаляет подписчиков через блоки add/remove, оба из которых должны присутствовать. Такое объявление часто используется для создания своего механизма событий с сохранением удобств языка C# в работе с ними.
Например, одна из типичных реализаций заключается в отдельном хранении словаря делегатов событий, в котором присутствуют только те делегаты, на события которых была осуществлена подписка. Доступ к словарю осуществляется по ключам, которыми обычно являются статические поля типа object, используемые только для сравнения их ссылок. Это делается для того, чтобы уменьшить количество памяти, занимаемое экземпляром класса (в случае, если он содержит большое количество нестатических событий). Эта реализация применяется в WinForms.

Как происходит подписка на событие и его вызов?

Все действия по подписке и отписке (обозначаются как += и -=, можно легко спутать с операторами делегатов) компилируются в вызовы методов add и remove. Вызовы внутри класса, отличные от вышеуказанных, компилируются в простую работу с делегатом. Следует заметить, что при неявной (и при правильной явной) реализации события невозможно получить доступ к делегату извне класса, работать можно лишь с событием как с абстракцией — подписываясь и отписываясь. Так как нет способа определить, подписались ли мы на какое-либо событие (если не использовать рефлексию), то кажется логичным, что отписка от него никогда не вызовет ошибок — можно смело отписываться, даже если делегат события пуст.

Модификаторы событий

Для событий могут использоваться модификаторы области видимости (public, protected, private, internal), они могут быть перекрыты (virtual, override, sealed) или не реализованы (abstract, extern). Событие может перекрывать событие с таким же именем из базового класса (new) или быть членом класса (static). Если событие объявлено и с модификатором override и с модификатором abstract одновременно, то наследники класса должны будут переопределить его (равно как и методы или свойства с этими двумя модификаторами).

Какие типы событий бывают?

Как уже было отмечено, тип события всегда должен быть типом делегата. Стандартными типами для событий являются типы EventHandler и EventHandler где TEventArgs — наследник EventArgs. Тип EventHandler используется когда аргументов события не предусмотрено, а тип EventHandler — когда аргументы события есть, тогда для них создается отдельный класс — наследник от EventArgs. Также можно использовать любые другие типы делегатов, но применение типизированного EventHandler выглядит более логичным и красивым.

Как все обстоит в C# 3?

Реализация field-like события, которая описана выше, соответствует языку C# 4 (.NET 4.0). Для более ранних версий существуют весьма существенные отличия.
Неявная реализация использует lock(this) для обеспечения потокобезопасности вместо Interlocked.CompareExchange с циклом. Для статических событий используется lock(typeof(Class)). Вот код, аналогичный раскрытому компилятором неявному определению события в C# 3:

class Class < EventHandler changed; public event EventHandler Changed < add < lock(this) < changed = changed + value; >> remove < lock(this) < changed = changed - value; >> > > 

Помимо этого, работа с событием внутри класса ведется как с делегатом, т.е. += и -= вызывают Delegate.Combine и Delegate.Remove напрямую, в обход методов add/remove. Это изменение может привести к невозможности сборки проекта на языке C# 4! В C# 3 результатом += и -= был делегат, т.к. результатом присвоения переменной всегда является присвоенное значение. В C# 4 результатом является void, т.к. методы add/remove не возвращают значения.

Помимо изменений в работе на разных версиях языка есть еще несколько особенностей.

Особенность №1 — продление времени жизни подписчика

При подписке на событие мы добавляем в список вызовов делегата события ссылку на метод, который будет вызван при вызове события. Таким образом, память, занимаемая объектом, подписавшимся на событие, не будет освобождена до его отписки от события или до уничтожения объекта, заключающего в себе событие. Эта особенность является одной из часто встречаемых причин утечек памяти в приложениях.
Для исправления этого недостатка часто используются weak events, слабые события. Эта тема уже была освещена на Хабре.

Особенность №2 — явная реализация интерфейса

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

EventHandler changed; event EventHandler ISomeInterface.Changed < add < changed += value; >remove < changed -= value; >> 
Особенность №3 — безопасный вызов

События перед вызовом следует проверять на null, что следует из описанной выше работы делегатов. От этого разрастается код, для избежания чего существует как минимум два способа. Первый способ описан Джоном Скитом (Jon Skeet) в его книге C# in depth:

public event EventHandler Changed = delegate < >; 

Коротко и лаконично. Мы инициализируем делегат события пустым методом, поэтому он никогда не будет null. Вычесть из делегата этот метод невозможно, т.к. он определен при инициализации делегата и у него нет ни имени, ни ссылки на него из любого места программы.

Второй способ заключается в написании метода, содержащего в себе необходимую проверку на null. Этот прием особенно хорошо работает в .NET 3.5 и выше, где доступны методы расширений (extension methods). Так как при вызове метода расширений объект, на котором он вызывается, является всего лишь параметром этого метода, то этот объект может быть пустой ссылкой, что и используется в данном случае.

public static class EventHandlerExtensions < public static void SafeRaise(this EventHandler handler, object sender, EventArgs e) < if(handler != null) handler(sender, e); >public static void SafeRaise(this EventHandler handler, object sender, TEventArgs e) where TEventArgs : EventArgs < if(handler != null) handler(sender, e); >> 

Таким образом, мы можем вызывать события как Changed.SafeRaise(this, EventArgs.Empty), что экономит нам строчки кода. Также можно определить третий вариант метода расширений для случая, когда у нас EventArgs.Empty, чтобы не передавать их явно. Тогда код сократится до Changed.SafeRaise(this), но я не буду рекомендовать такой подход, т.к. для других членов вашей команды это может быть не так явно, как передача пустого аргумента.

Тонкость №4 — что не так со стандартной реализацией?

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

Бонус: попытка Microsoft сделать контравариантные события

В первой бете C# 4 Microsoft попытались добавить контравариантности событиям. Это позволяло подписываться на событие EventHandler методами, имеющими сигнатуру EventHandler и все работало до тех пор, пока в делегат события не добавлялось несколько методов с разной (но подходящей) сигнатурой. Такой код компилировался, но падал с ошибкой времени выполнения. По всей видимости, обойти это так и не смогли и в релизе C# 4 контравариантность для EventHandler была отключена.
Это не так заметно, если опускать явное создание делегата при подписке, например следующий код отлично скомпилируется:

public class Tests < public event EventHandlerChanged; public void Test() < Changed += ChangedMyEventArgs; Changed += ChangedEventArgs; >void ChangedMyEventArgs(object sender, MyEventArgs e) < >void ChangedEventArgs(object sender, EventArgs e) < >> 

Это происходит потому, что компилятор сам подставит new EventHandler(. ) к обеим подпискам. Если же хотя бы в одном из мест использовать new EventHandler(. ), то компилятор сообщит об ошибке — невозможно сконвертировать тип EventHandler в EventHandler (здесь Events — пространство имен моего тестового проекта).

Источники
  • Jon Skeet. C# in Depth, Second Edition
  • Chris Burrows. Events get a little overhaul in C# 4, Part I: Locks
  • Chris Burrows. Events get a little overhaul in C# 4, Part II: Semantic Changes and +=/-=
  • Chris Burrows. Events get a little overhaul in C# 4, Part III: Breaking Changes
  • Chris Burrows. Events get a little overhaul in C# 4, Afterward: Effective Events
  • MSDN. 10.7 Events — часть спецификации языка C# для .NET 1.1
  • MSDN. How to: Handle Multiple Events Using Event Properties
  • JetBrains ReSharper. Delegate subtraction has unpredictable semantics
  • StackOverflow Question. Event and delegate contravariance in .NET 4.0 and C# 4.0

Как пользоваться object sender?

Как программно вызвать событие «AfterCheck (object sender, TreeViewEventArgs e)» для Treview?
Как программно вызвать событие "AfterCheck (object sender, TreeViewEventArgs e)" для Treview? Что.

Object sender вытащить информацию
Добрый день, нужна информация из object sender так как данные передаются между библиотеками.

Работа с (object sender, EventArgs e)
Добрый день. Помогите разобраться. Я объединил 9 лэйблов и создал для них событие Click, назвал его.

Как правильно использовать sender
Такая ситуация. Я в коде создаю некоторое количество обьектов Panel, а в одном Panel по 2 обьекта.

6046 / 3455 / 335
Регистрация: 14.06.2009
Сообщений: 8,136
Записей в блоге: 2

Лучший ответ

Сообщение было отмечено q13m00 как решение

Решение

sender — это указатель на объект вызвавший это событие.

Добавлено через 2 минуты
Для твоего случая реализация такая

1 2 3 4 5
private void textBox_Click(object sender, EventArgs e) { if(sender is TextBox) (sender as TextBox).Text = "123"; }

87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь

object sender .
перетаскиваю текст из текстового поля в другое. Чтобы не писать процедуры для всех других.

Object sender
Добрый день, такой вопрос. При создании функции(события) в параметры этой функции передается Object.

Sender object для Control.Paint
Доброго времени суток! Нужна ваша помощь. Существует метод private void Plane_Paint(object.

Аргументы object sender и EventArgs e в функциях
Здравствуйте, уважаемые форумчане) Нужно очень детально прокоментировать код к курсачу, а что это.

Object sender c что это

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

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

class Account < // сумма на счете public int Sum < get; private set; >// в конструкторе устанавливаем начальную сумму на счете public Account(int sum) => Sum = sum; // добавление средств на счет public void Put(int sum) => Sum += sum; // списание средств со счета public void Take(int sum) < if (Sum >= sum) < Sum -= sum; >> >

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

Account account = new Account(100); account.Put(20); // добавляем на счет 20 Console.WriteLine($"Сумма на счете: "); account.Take(70); // пытаемся снять со счета 70 Console.WriteLine($"Сумма на счете: "); account.Take(180); // пытаемся снять со счета 180 Console.WriteLine($"Сумма на счете: ");
Сумма на счете: 120 Сумма на счете: 50 Сумма на счете: 50

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

public void Put(int sum) < Sum += sum; Console.WriteLine($"На счет поступило: "); >

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

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

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

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

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

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

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

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

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

if(Notify !=null) Notify("Произошло действие");
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($"На счет поступило: "); // 2.Вызов события > public void Take(int sum) < if (Sum >= sum) < Sum -= sum; Notify?.Invoke($"Со счета снято: "); // 2.Вызов события > else < Notify?.Invoke($"Недостаточно денег на счете. Текущий баланс: "); ; > > >

Теперь с помощью события Notify мы уведомляем систему о том, что были добавлены средства и о том, что средства сняты со счета или на счете недостаточно средств.

Добавление обработчика события

С событием может быть связан один или несколько обработчиков. Обработчики событий — это именно то, что выполняется при вызове событий. Нередко в качестве обработчиков событий применяются методы. Каждый обработчик событий по списку параметров и возвращаемому типу должен соответствовать делегату, который представляет событие. Для добавления обработчика события применяется операция += :

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

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

Account account = new Account(100); account.Notify += DisplayMessage; // Добавляем обработчик для события Notify account.Put(20); // добавляем на счет 20 Console.WriteLine($"Сумма на счете: "); account.Take(70); // пытаемся снять со счета 70 Console.WriteLine($"Сумма на счете: "); account.Take(180); // пытаемся снять со счета 180 Console.WriteLine($"Сумма на счете: "); 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 в отдельную библиотеку классов и добавлять в любой проект.

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

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

Account account = new Account(100); account.Notify += DisplayMessage; // добавляем обработчик DisplayMessage account.Notify += DisplayRedMessage; // добавляем обработчик DisplayRedMessage account.Put(20); // добавляем на счет 20 account.Notify -= DisplayRedMessage; // удаляем обработчик DisplayRedMessage account.Put(50); // добавляем на счет 50 void DisplayMessage(string message) => Console.WriteLine(message); void DisplayRedMessage(string message) < // Устанавливаем красный цвет символов Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(message); // Сбрасываем настройки цвета Console.ResetColor(); >
На счет поступило: 20 На счет поступило: 20 На счет поступило: 50

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

Account acc = new Account(100); // установка делегата, который указывает на метод DisplayMessage acc.Notify += new Account.AccountHandler(DisplayMessage); // установка в качестве обработчика метода DisplayMessage acc.Notify += DisplayMessage; // добавляем обработчик DisplayMessage acc.Put(20); // добавляем на счет 20 void DisplayMessage(string message) => Console.WriteLine(message);

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

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

Account acc = new Account(100); acc.Notify += delegate (string mes) < Console.WriteLine(mes); >; acc.Put(20);

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

Account account = new Account(100); account.Notify += message => Console.WriteLine(message); account.Put(20);

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

С помощью специальных акссесоров add/remove мы можем управлять добавлением и удалением обработчиков. Как правило, подобная функциональность редко требуется, но тем не менее мы ее можем использовать. Например:

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

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

AccountHandler notify;

Во второй части определяем акссесоры add и remove. Аксессор add вызывается при добавлении обработчика, то есть при операции +=. Добавляемый обработчик доступен через ключевое слово value . Здесь мы можем получить информацию об обработчике (например, имя метода через value.Method.Name) и определить некоторую логику. В данном случае для простоты просто выводится сообщение на консоль:

add < notify += value; Console.WriteLine($"добавлен"); >

Блок remove вызывается при удалении обработчика. Аналогично здесь можно задать некоторую дополнительную логику:

remove < notify -= value; Console.WriteLine($"удален"); >

Внутри класса событие вызывается также через переменную notify. Но для добавления и удаления обработчиков в программе используется как раз Notify:

Account acc = new Account(100); acc.Notify += DisplayMessage; // добавляем обработчик DisplayMessage acc.Put(20); // добавляем на счет 20 acc.Notify -= DisplayMessage; // удаляем обработчик DisplayMessage acc.Put(20); // добавляем на счет 20 void DisplayMessage(string message) => Console.WriteLine(message);

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

DisplayMessage добавлен На счет поступило: 20 DisplayMessage удален

Передача данных события

Нередко при возникновении события обработчику события требуется передать некоторую информацию о событии. Например, добавим и в нашу программу новый класс AccountEventArgs со следующим кодом:

class AccountEventArgs < // Сообщение public string Message// Сумма, на которую изменился счет public int Sum public AccountEventArgs(string message, int sum) < Message = message; Sum = sum; >>

Данный класс имеет два свойства: Message — для хранения выводимого сообщения и Sum — для хранения суммы, на которую изменился счет.

Теперь применим класс AccoutEventArgs, изменив класс Account следующим образом:

class Account < public delegate void AccountHandler(Account sender, AccountEventArgs e); public event AccountHandler? Notify; public int Sum < get; private set; >public Account(int sum) => Sum = sum; public void Put(int sum) < Sum += sum; Notify?.Invoke(this, new AccountEventArgs($"На счет поступило ", sum)); > public void Take(int sum) < if (Sum >= sum) < Sum -= sum; Notify?.Invoke(this, new AccountEventArgs($"Сумма снята со счета", sum)); > else < Notify?.Invoke(this, new AccountEventArgs("Недостаточно денег на счете", sum)); >> >

По сравнению с предыдущей версией класса Account здесь изменилось только количество параметров у делегата и соответственно количество параметров при вызове события. Теперь делегат AccountHandler в качестве первого параметра принимает объект, который вызвал событие, то есть текущий объект Account. А в качестве второго параметра принимает объект AccountEventArgs, который хранит информацию о событии, получаемую через конструктор.

Теперь изменим основную программу:

Account acc = new Account(100); acc.Notify += DisplayMessage; acc.Put(20); acc.Take(70); acc.Take(150); void DisplayMessage(Account sender, AccountEventArgs e) < Console.WriteLine($"Сумма транзакции: "); Console.WriteLine(e.Message); Console.WriteLine($"Текущая сумма на счете: "); >

По сравнению с предыдущим вариантом здесь мы только изменяем количество параметров и их использования в обработчике DisplayMessage. Благодаря первому параметру в методе можно получить информацию об отправителе события — счете, с которым производится операция. А через второй параметр можно получить инфомацию о состоянии операции.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *