C# Language
События
Поиск…
Вступление
Событие - это уведомление о том, что что-то произошло (например, щелчок мыши) или, в некоторых случаях, произойдет (например, изменение цены).
Классы могут определять события, и их экземпляры (объекты) могут создавать эти события. Например, кнопка может содержать событие Click, которое поднимается, когда пользователь щелкнул его.
Обработчики событий - это методы, которые вызываются при возникновении соответствующего события. Форма может содержать обработчик события Clicked для каждой кнопки, которая содержит, например.
параметры
параметр | подробности |
---|---|
EventArgsT | Тип, который выводится из EventArgs и содержит параметры события. |
Название события | Название события. |
HandlerName | Имя обработчика события. |
SenderObject | Объект, вызывающий событие. |
EventArguments | Экземпляр типа EventArgsT, который содержит параметры события. |
замечания
При поднятии события:
- Всегда проверяйте, является ли делегат
null
. Нулевой делегат означает, что у события нет подписчиков. Поднятие события без подписчиков приведет кNullReferenceException
.
- Скопируйте делегат (например,
EventName
) в локальную переменную (например,eventName
), прежде чем проверять значение null / повышение события. Это позволяет избежать условий гонки в многопоточных средах:
Неправильно :
if(Changed != null) // Changed has 1 subscriber at this point
// In another thread, that one subscriber decided to unsubscribe
Changed(this, args); // `Changed` is now null, `NullReferenceException` is thrown.
Справа :
// Cache the "Changed" event as a local. If it is not null, then use
// the LOCAL variable (handler) to raise the event, NOT the event itself.
var handler = Changed;
if(handler != null)
handler(this, args);
- Используйте оператор с нулевым условием (?.) Для повышения метода вместо нулевой проверки делегата для подписчиков в выражении
if
:EventName?.Invoke(SenderObject, new EventArgsT());
- При использовании Action <> для объявления типов делегатов подпись анонимного метода / события должна быть такой же, как объявленный анонимный тип делегата в объявлении события.
Объявление и повышение активности
Объявление события
Вы можете объявить событие в любом class
или struct
используя следующий синтаксис:
public class MyClass
{
// Declares the event for MyClass
public event EventHandler MyEvent;
// Raises the MyEvent event
public void RaiseEvent()
{
OnMyEvent();
}
}
Существует расширенный синтаксис для объявления событий, в котором вы держите частный экземпляр события и определяете открытый экземпляр, используя add
и set
accessors. Синтаксис очень похож на свойства C #. Во всех случаях синтаксис, продемонстрированный выше, должен быть предпочтительным, поскольку компилятор испускает код, чтобы гарантировать, что несколько потоков могут безопасно добавлять и удалять обработчики событий в событие на вашем классе.
Поднятие мероприятия
private void OnMyEvent()
{
EventName?.Invoke(this, EventArgs.Empty);
}
private void OnMyEvent()
{
// Use a local for EventName, because another thread can modify the
// public EventName between when we check it for null, and when we
// raise the event.
var eventName = EventName;
// If eventName == null, then it means there are no event-subscribers,
// and therefore, we cannot raise the event.
if(eventName != null)
eventName(this, EventArgs.Empty);
}
Обратите внимание, что события могут быть подняты только типом объявления. Клиенты могут подписаться или отказаться от подписки.
Для версий C # до 6.0, где EventName?.Invoke
не поддерживается, рекомендуется назначить событие временной переменной перед вызовом, как показано в примере, что обеспечивает безопасность потоков в случаях, когда несколько потоков выполняют одинаковые код. В противном случае может возникнуть NullReferenceException
в некоторых случаях, когда несколько потоков используют один и тот же экземпляр объекта. В C # 6.0 компилятор испускает код, подобный тому, который показан в примере кода для C # 6.
Стандартное объявление о мероприятии
Объявление события:
public event EventHandler<EventArgsT> EventName;
Объявление обработчика события:
public void HandlerName(object sender, EventArgsT args) { /* Handler logic */ }
Подписка на событие:
Динамически:
EventName += HandlerName;
Через конструктора:
- Нажмите кнопку «События» в окне свойств элемента управления («Молниеносный болт»)
- Дважды щелкните имя события:
- Visual Studio создаст код события:
private void Form1_Load(object sender, EventArgs e)
{
}
Вызов метода:
EventName(SenderObject, EventArguments);
Объявление анонимного обработчика событий
Объявление события:
public event EventHandler<EventArgsType> EventName;
Объявление обработчика события с помощью lambda operator => и подписки на событие:
EventName += (obj, eventArgs) => { /* Handler logic */ };
Объявление обработчика события с использованием синтаксиса анонимного делегата делегата :
EventName += delegate(object obj, EventArgsType eventArgs) { /* Handler Logic */ };
Декларация и подписка обработчика событий, который не использует параметр события, и поэтому может использовать указанный выше синтаксис, не указывая параметры:
EventName += delegate { /* Handler Logic */ }
Вызов события:
EventName?.Invoke(SenderObject, EventArguments);
Нестандартная заявка на мероприятие
События могут иметь любой тип делегата, а не только EventHandler
и EventHandler<T>
. Например:
//Declaring an event
public event Action<Param1Type, Param2Type, ...> EventName;
Это используется аналогично стандартным событиям EventHandler
:
//Adding a named event handler
public void HandlerName(Param1Type parameter1, Param2Type parameter2, ...) {
/* Handler logic */
}
EventName += HandlerName;
//Adding an anonymous event handler
EventName += (parameter1, parameter2, ...) => { /* Handler Logic */ };
//Invoking the event
EventName(parameter1, parameter2, ...);
Можно объявить несколько событий одного и того же типа в одном выражении, аналогично полям и локальным переменным (хотя это часто может быть плохой идеей):
public event EventHandler Event1, Event2, Event3;
Это объявляет три отдельных события ( Event1
, Event2
и Event3
) для всех типов EventHandler
.
Примечание. Хотя некоторые компиляторы могут принять этот синтаксис как в интерфейсах, так и в классах, спецификация C # (v5.0 §13.2.3) предоставляет грамматику для интерфейсов, которые этого не позволяют, поэтому использование этого в интерфейсах может быть ненадежным для разных компиляторов.
Создание настраиваемых EventArg, содержащих дополнительные данные
Для пользовательских событий обычно требуются настраиваемые аргументы событий, содержащие информацию о событии. Например MouseEventArgs
, который используется на события мыши , как MouseDown
или MouseUp
событий, содержит информацию о Location
или Buttons
, которые используются для генерации события.
При создании новых событий для создания настраиваемого события arg:
- Создайте класс, полученный из
EventArgs
и определите свойства необходимых данных. - В качестве условного обозначения имя класса должно заканчиваться
EventArgs
.
пример
В приведенном ниже примере мы создаем событие PriceChangingEventArgs
для свойства Price
для класса. Класс данных событий содержит CurrentPrice
и NewPrice
. Событие возникает, когда вы назначаете новое значение для свойства Price
и позволяет потребителю знать, что значение меняется и позволяет узнать о текущей цене и новой цене:
PriceChangingEventArgs
public class PriceChangingEventArgs : EventArgs
{
public PriceChangingEventArgs(int currentPrice, int newPrice)
{
this.CurrentPrice = currentPrice;
this.NewPrice = newPrice;
}
public int CurrentPrice { get; private set; }
public int NewPrice { get; private set; }
}
Товар
public class Product
{
public event EventHandler<PriceChangingEventArgs> PriceChanging;
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = value;
}
}
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PriceChanging;
if (handler != null)
handler(this, e);
}
}
Вы можете улучшить пример, разрешив потребителю изменить новое значение, а затем значение будет использовано для свойства. Для этого достаточно применить эти изменения в классах.
Измените определение NewPrice
:
public int NewPrice { get; set; }
Измените определение Price
на использование e.NewPrice
качестве значения свойства после вызова OnPriceChanging
:
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(price, value);
OnPriceChanging(e);
price = e.NewPrice;
}
}
Создание отменяемого события
Отменное событие может быть поднято классом, когда он собирается выполнить действие, которое может быть отменено, например событие FormClosing
Form
.
Чтобы создать такое событие:
- Создайте новое событие arg from
CancelEventArgs
и добавьте дополнительные свойства для данных события. - Создайте событие, используя
EventHandler<T>
и используйте созданный вами класс arg нового события cancel.
пример
В приведенном ниже примере мы создаем событие PriceChangingEventArgs
для свойства Price
для класса. Класс данных события содержит Value
которое позволяет потребителю узнать о новом. Событие возникает, когда вы назначаете новое значение для свойства Price
и позволяет потребителю знать, что значение меняется, и позволить им отменить событие. Если потребитель отменяет событие, будет использоваться предыдущее значение для Price
:
PriceChangingEventArgs
public class PriceChangingEventArgs : CancelEventArgs
{
int value;
public int Value
{
get { return value; }
}
public PriceChangingEventArgs(int value)
{
this.value = value;
}
}
Товар
public class Product
{
int price;
public int Price
{
get { return price; }
set
{
var e = new PriceChangingEventArgs(value);
OnPriceChanging(e);
if (!e.Cancel)
price = value;
}
}
public event EventHandler<PriceChangingEventArgs> PropertyChanging;
protected void OnPriceChanging(PriceChangingEventArgs e)
{
var handler = PropertyChanging;
if (handler != null)
PropertyChanging(this, e);
}
}
Свойства события
Если класс вызывает большое количество событий, стоимость хранения одного поля на одного делегата может быть неприемлемым. .NET Framework предоставляет свойства событий для этих случаев. Таким образом, вы можете использовать другую структуру данных, такую как EventHandlerList
для хранения делегатов событий:
public class SampleClass
{
// Define the delegate collection.
protected EventHandlerList eventDelegates = new EventHandlerList();
// Define a unique key for each event.
static readonly object someEventKey = new object();
// Define the SomeEvent event property.
public event EventHandler SomeEvent
{
add
{
// Add the input delegate to the collection.
eventDelegates.AddHandler(someEventKey, value);
}
remove
{
// Remove the input delegate from the collection.
eventDelegates.RemoveHandler(someEventKey, value);
}
}
// Raise the event with the delegate specified by someEventKey
protected void OnSomeEvent(EventArgs e)
{
var handler = (EventHandler)eventDelegates[someEventKey];
if (handler != null)
handler(this, e);
}
}
Этот подход широко используется в графических интерфейсах, таких как WinForms, где элементы управления могут иметь десятки и даже сотни событий.
Обратите внимание, что EventHandlerList
не является потокобезопасным, поэтому, если вы ожидаете, что ваш класс будет использоваться из нескольких потоков, вам нужно будет добавить операторы блокировки или другой механизм синхронизации (или использовать хранилище, обеспечивающее безопасность потоков).