Как сказал Крис Грей, одно из применений - сигнализировать, когда что-то произошло, что ваш код не вызывал напрямую. Наиболее распространенной причиной здесь, вероятно, являются действия пользователя в графическом интерфейсе. Другим примером может быть асинхронная операция, завершающаяся в другом потоке.
Другая причина использовать события - это когда вы не знаете, кого может заинтересовать то, что только что произошло. Класс, вызывающий событие, не должен знать (во время разработки) что-либо о том, сколько экземпляров других классов может заинтересовать.
class Raiser {
public DoSomething() {
//Do something long winded.
OnDidSomething(new DidSomethingEventArgs());
}
public EventHandler<DidSomethingEventArgs> DidSomething;
private OnDidSomething(DidSomethingEventArgs e) {
if (DidSomething != null)
DidSomething(this, e);
}
}
Очевидно, вам также нужно определить класс DidSomethingEventArgs, который передает соответствующие данные о событии. Это также иллюстрирует общее соглашение об именах для событий. Если событие называется X, то событие возникает только в методе с именем OnX, и любые передаваемые им данные являются экземпляром класса XEventArgs. Обратите внимание, что событие может быть нулевым, если на него не подписаны слушатели, поэтому проверка перед тем, как мы вызовем событие.
Обратите внимание, что этот класс ничего не знает о том, какие другие классы могут быть заинтересованы в том, что он что-то сделал. Он просто объявляет о том, что сделал это.
Несколько классов могут прослушивать событие:
class ListenerA {
private Raiser r;
ListenerA(Raiser r) {
this.r = r;
r.DidSomething += R_DidSomething;
}
R_DidSomething(object sender, DidSomethingEventArgs e) {
//Do something with the result.
}
}
И
class ListenerB {
private Raiser r;
ListenerB(Raiser r) {
this.r = r;
r.DidSomething += R_DidSomething;
}
R_DidSomething(object sender, DidSomethingEventArgs e) {
//Do something with the result.
}
}
Теперь, когда метод DoSomething вызывается для экземпляра Raiser, все экземпляры ListenerA и ListenerB будут уведомляться через событие DidSomething. Обратите внимание, что классы слушателей могут легко находиться в разных сборках с рейзером Им нужна ссылка обратно на сборку сборщика, но не нужна ссылка на сборку его слушателей.
Обратите внимание, что приведенный выше простой пример Raiser может вызвать некоторые проблемы в многопоточной программе. Более надежный пример будет использовать что-то вроде:
class Raiser {
public DoSomething() {
//Do something long winded.
OnDidSomething(new DidSomethingEventArgs());
}
#region DidSomething Event
private object _DidSomethingLock = new object();
private EventHandler<DidSomethingEventArgs> _DidSomething;
public EventHandler<DidSomethingEventArgs> DidSomething {
add { lock(_DidSomethinglock) _DidSomething += value; }
remove { lock(_DidSomethinglock) _DidSomething -= value; }
}
OnDidSomething(DidSomethingEventArgs e) {
EventHandler<DidSomethingEventArgs> handler;
lock (_DidSomethingLock)
handler = _DidSomething;
if (handler == null)
return;
try {
DidSomething(this, e);
} catch (Exception ex) {
//Do something with the exception
}
}
#endregion
}
Это гарантирует, что другой поток, добавляющий или удаляющий слушателя, пока вы находитесь в процессе вызова события, не вызовет проблем.
Простые слушатели, используемые здесь, также вызовут утечки памяти, если экземпляры классов слушателей создаются и уничтожаются. Это связано с тем, что экземпляр Raiser получает (и сохраняет) ссылку на каждого слушателя, когда он подписывается на событие. Этого достаточно, чтобы не дать сборщику мусора должным образом привести в порядок слушателей, когда все явные ссылки на них удалены. Лучший способ обойти это, вероятно, заставить слушателей реализовать интерфейс IDisposable и отказаться от подписки на события в методе Dispose. Тогда вам просто нужно не забыть вызвать метод Dispose.