Как отказаться от всех обработчиков из события для определенного класса в С#?
Основная предпосылка:
У меня есть Комната, в которой публикуется событие, когда Аватар "входит" ко всем Аватарам в Комнате. Когда Аватар покидает комнату, я хочу, чтобы он удалил все подписки для этой комнаты.
Как я могу лучше всего отменить аватар из всех событий в комнате, прежде чем добавить Аватар в новую комнату и подписаться на новые события Room?
Код выглядит примерно так:
class Room
{
public event EventHandler<EnterRoomEventArgs> AvatarEntersRoom;
public event EvnetHandler<LeaveRoomEventArgs> AvatarLeavesRoom;
public event EventHandler<AnotherOfManyEventArgs> AnotherOfManayAvatarEvents;
public void AddPlayer(Avatar theAvatar)
{
AvatarEntersRoom(this, new EnterRoomEventArgs());
AvatarEntersRoom += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
AvatarLeavesRoom += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
AnotherOfManayAvatarEvents += new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
}
}
class Avatar
{
public void HandleAvatarEntersRoom(object sender, EnterRoomEventArgs e)
{
Log.Write("avatar has entered the room");
}
public void HandleAvatarLeaveRoom(object sender, LeaveRoomEventArgs e)
{
Log.Write("avatar has left room");
}
public void HandleAnotherOfManayAvatarEvents(object sender, AnotherOfManyEventArgs e)
{
Log.Write("another avatar event has occurred");
}
}
Ответы
Ответ 1
Каждый делегат имеет метод с именем GetInvocationList()
, который возвращает все фактические зарегистрированные делегаты. Итак, если предположить, что тип (или событие) делегата имеет имя say MyDelegate
, а переменная экземпляра обработчика называется myDlgHandler
, вы можете написать:
Delegate[] clientList = myDlgHandler.GetInvocationList();
foreach (var d in clientList)
myDlgHandler -= (d as MyDelegate);
чтобы покрыть случай, когда он может быть нулевым,
if(myDlgHandler != null)
foreach (var d in myDlgHandler.GetInvocationList())
myDlgHandler -= (d as MyDelegate);
Ответ 2
Что-то не так со стандартным удалением?
public void RemovePlayer(Avatar theAvatar) {
AvatarEntersRoom -= new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);
}
ИЗМЕНИТЬ
Основываясь на вашем обновлении, вам кажется, что вам нужен код, который удалит определенный объект из всех событий в определенном классе. Нет реалистичного способа достижения этой цели. Это часто немного подробный, но наилучшим способом является индивидуальное добавление/удаление конкретного метода метода объекта из каждого события.
Единственный способ получить закрыть для этой функции - использовать отражение. Вы могли бы отразить все события в своем классе, а затем сделать магию, чтобы найти все экземпляры класса в цепочке событий. Это будет лишь частичным решением, потому что оно будет игнорировать такие вещи, как обработчики событий выражения лямбда.
Ответ 3
Вероятно, самым простым способом добиться этого было бы сохранить все ваши подписанные события для аватара в ArrayList
делегатов для событий.
Когда аватар покидает комнату, просто прокрутите список делегатов, выполняющих стандартный remove (-=
).
Ответ 4
вы можете запускать всех подписчиков событий с помощью:
_Event.GetInvocationList()
и удалите каждый обработчик событий.
Delegate[] subscribers = myEvent.GetInvocationList();
for(int i = 0; i < subscribers.Length; i++)
{
myEvent -= subscribers[i] as yourDelegateType;
}
Ответ 5
То, что я хотел бы сделать, - это отладка (не думайте, что это хорошая производительность для релиза, и ее нужно ловить во время разработки), генерировать исключения, когда события класса не отписываются, это метод, который я использую:
#if DEBUG
private void CheckEventHasNoSubscribers(Delegate eventDelegate)
{
if (eventDelegate != null)
if (eventDelegate.GetInvocationList().Length != 0)
{
var subscriberCount = eventDelegate.GetInvocationList().Length;
// determine the consumers of this event
var subscribers = new StringBuilder();
foreach (var del in eventDelegate.GetInvocationList())
subscribers.AppendLine((subscribers.Length != 0 ? ", " : "") + del.Target);
// throw an exception listing all current subscription that would hinder GC on them!
throw new Exception(
$"Event:{eventDelegate.Method.Name} still has {subscriberCount} subscribers, with the following targets [{subscribers}]");
}
}
#endif
В разделе "Утилизация" элемента, которому принадлежит делегат, или в любом другом месте, где ваш рабочий процесс должен освободить объект, я бы назвал его следующим образом.
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (_orderCacheLock != null)
_orderCacheLock.Dispose();
if(_SettingTradeTimeOut!=null)
_SettingTradeTimeOut.Dispose();
_orderCacheLock = null;
#if DEBUG
CheckEventHasNoSubscribers(OnIsProfitable);
CheckEventHasNoSubscribers(OnPropertyChanged);
#endif
disposedValue = true;
}
}
Тогда очень легко найти подписчиков на эти "осиротевшие" события и исправить код
пс:
Расширение этого "образца практики" выглядит следующим образом.
public static void CheckEventHasNoSubscribers(this Delegate eventDelegate)
{
if (eventDelegate != null)
if (eventDelegate.GetInvocationList().Length != 0)
{
var subscriberCount = eventDelegate.GetInvocationList().Length;
// determine the consumers of this event
var subscribers = new StringBuilder();
foreach (var del in eventDelegate.GetInvocationList())
subscribers.AppendLine((subscribers.Length != 0 ? ", " : "") + del.Target);
// point to the missing un-subscribed events
throw new Exception( $"Event:{eventDelegate.Method.Name} still has {subscriberCount} subscribers, with the following targets [{subscribers}]");
}
}