简体   繁体   English

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

[英]How do I unsubscribe all handlers from an event for a particular class in C#?

Basic premise: 基本前提:

I have a Room which publishes an event when an Avatar "enters" to all Avatars within the Room. 我有一个房间,当一个阿凡达“进入”房间内的所有头像时,它会发布一个事件。 When an Avatar leaves the Room I want it to remove all subscriptions for that room. 当阿凡达离开房间时,我希望它删除该房间的所有订阅。

How can I best unsubscribe the Avatar from all events in the room before I add the Avatar to a new Room and subscribe to the new Room's events? 在将“阿凡达”添加到新房间并订阅新房间的活动之前,我怎样才能最好地取消订阅房间中所有活动的阿凡达?

The code goes something like this: 代码如下:

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

Each delegate has a method named GetInvocationList() that returns all the actual delegates that have been registered. 每个委托都有一个名为GetInvocationList()的方法,它返回已注册的所有实际委托。 So, assuming the delegate Type (or event) is named say MyDelegate , and the handler instance variable is named myDlgHandler , you can write: 因此,假设委托类型(或事件)被命名为MyDelegate ,并且处理程序实例变量名为myDlgHandler ,您可以编写:

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

to cover the case where it might be null, 覆盖它可能为null的情况,

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

Is there anything wrong with a standard remove? 标准删除有什么问题吗?

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

}

EDIT 编辑

Based on your update it appears that you want code that will remove a particular object from all events on a particular class. 根据您的更新,您似乎希望代码能够从特定类的所有事件中删除特定对象。 There is no realistic way to accomplish this goal. 没有现实的方法来实现这一目标。 It's often a bit verbose but the best way is to individually add/remove a particular object method combo from every event. 它通常有点冗长,但最好的方法是从每个事件中单独添加/删除特定的对象方法组合。

The only way to get close to this functionality is to use reflection. 接近此功能的唯一方法是使用反射。 You could reflectively grab all events on your class and then do some magic to find all instances of a class within the event chain. 你可以反思地抓住你班上的所有事件,然后做一些魔术来找到事件链中所有类的实例。 This will only be a partial solution though because it will ignore such things as a lambda expression event handlers. 这只是一个部分解决方案,因为它会忽略诸如lambda表达式事件处理程序之类的东西。

Probably the simplest way to accomplish this would be to store all of your subscribed events for an avatar in an ArrayList of delegates to the events. 实现此目的的最简单方法可能是将所有订阅的事件存储在事件的委托的ArrayList中。

When the avatar leaves the room, simply loop through the list of delegates performing a standard remove ( -= ). 当化身离开房间时,只需循环执行标准删除( -= )的代表列表。

you can run on all the event subscribers with: 您可以在所有活动订阅者上运行:

_Event.GetInvocationList()

and remove each event handler. 并删除每个事件处理程序。

Delegate[] subscribers = myEvent.GetInvocationList();

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

What I'd like to do is in debug (do not think that this is good performance wise for release and one should catch it during development) throw exceptions when a class's events are not unsubscribed, this is the method that I use: 我想要做的是在调试中(不要认为这对于发布是明智的,并且应该在开发期间捕获它)当类的事件没有取消订阅时抛出异常,这是我使用的方法:

#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

The in my Dispose of the item that owns the delegate, or any other location where you're workflow supposed to release the object I would call it like this. 在我的Dispose中拥有委托的项目,或者你工作流程应该释放对象的任何其他位置,我会这样称呼它。

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

It's then super easy to find the subscribers to these "orphaned" events and fix the code 然后,很容易找到这些“孤立”事件的订阅者并修复代码

ps: An Extension of this "practice pattern" looks like this. ps:这种“练习模式”的扩展看起来像这样。

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

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM