如何取消订阅C#中特定类的事件的所有处理程序?

时间:2009-01-15 18:03:58

标签: c# .net events

基本前提:

我有一个房间,当一个阿凡达“进入”房间内的所有头像时,它会发布一个事件。当“阿凡达”离开房间时,我希望它删除该房间的所有订阅。

在将头像添加到新房间并订阅新房间的活动之前,我怎样才能最好地从房间内的所有活动中取消订阅“阿凡达”?

代码如下:

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");
   }
}

5 个答案:

答案 0 :(得分:50)

每个委托都有一个名为GetInvocationList()的方法,它返回已注册的所有实际委托。因此,假设委托类型(或事件)名为MyDelegate,并且处理程序实例变量名为myDlgHandler,则可以写:

Delegate[] clientList = myDlgHandler.GetInvocationList();
foreach (var d in clientList)
       myDlgHandler -= (d as MyDelegate);

涵盖可能为null的情况,

 if(myDlgHandler != null)
  foreach (var d in myDlgHandler.GetInvocationList())
       myDlgHandler -= (d as MyDelegate);

答案 1 :(得分:4)

标准删除有什么问题吗?

public void RemovePlayer(Avatar theAvatar) {
 AvatarEntersRoom -= new EventHandler<EnterRoomEventArgs>(theAvatar.HandleAvatarEntersRoom);

}

修改

根据您的更新,您似乎希望代码能够从特定类的所有事件中删除特定对象。没有现实的方法来实现这一目标。它通常有点冗长,但最好的方法是从每个事件中单独添加/删除特定的对象方法组合。

关闭此功能的唯一方法是使用反射。你可以反思地抓住你班上的所有事件,然后做一些魔术来找到事件链中所有类的实例。这只是一个部分解决方案,因为它会忽略诸如lambda表达式事件处理程序之类的东西。

答案 2 :(得分:4)

实现此目标的最简单方法可能是将所有订阅的事件存储在事件的ArrayList个代表中。

当头像离开房间时,只需循环执行标准删除(-=)的代表列表。

答案 3 :(得分:1)

您可以使用以下命令在所有活动订阅者上运行:

_Event.GetInvocationList()

并删除每个事件处理程序。

Delegate[] subscribers = myEvent.GetInvocationList();

for(int i = 0; i < subscribers.Length; i++)    
{    
    myEvent -= subscribers[i] as yourDelegateType;   
}

答案 4 :(得分:0)

我想做的是在调试中(不要认为这对于发布来说是一个很好的性能明智的选择,并且应该在开发过程中捕获它)在没有取消订阅类的事件时引发异常,这是我使用的方法:

def dec(fn):
    return fnB

class A:
    @dec
    def a(self):
        pass

A().a()
>>> <__main__.A object at 0x1051ba4a8>

print(A().a)
>>> <bound method fnB of <__main__.A object at 0x10d44e4a8>>

Works Great!! fnB has became bound method fnB But...

def dec(fn):
    return callable_b

class A:
    @dec
    def a(self):
        pass

A().a()
TypeError: __call__() missing 1 required positional argument: 'self_from_class_a'

print(A().a)
>>> <__main__.clsB object at 0x10f372470>

在“我的处置”中拥有委托的项目,或者工作流应该释放该对象的任何其他位置,我都会这样称呼它。

#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

然后很容易找到这些“孤立”事件的订户并修复代码

ps: 这种“练习模式”的扩展看起来像这样。

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;
    }
}

}