How do I unsubscribe all handlers from an event for a particular class in C#?
Asked Answered
K

5

42

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");
   }
}
Kassey answered 15/1, 2009 at 18:3 Comment(2)
Your code example is clouding your question. The most important word is the last word in the question "events", in particular that its plural. The code example ought to show multiple events and you desire to unregister easily from all of themDogeared
Does this answer your question? How can I clear event subscriptions in C#?Waltz
D
64

Each delegate has a method named GetInvocationList() that returns all the actual delegates that have been registered. So, assuming the delegate Type (or event) is named say MyDelegate, and the handler instance variable is named myDlgHandler, you can write:

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

to cover the case where it might be null,

 if(myDlgHandler != null)
  foreach (var d in myDlgHandler.GetInvocationList())
       myDlgHandler -= (d as MyDelegate);
Dark answered 15/1, 2009 at 18:46 Comment(10)
The null-conditional operators were added to C# 6.0 (not to .NET 4.0).Flagellant
The newest form might actually crash. If the myDlgHandler is null, the foreach will evaluate to foreach ( var d in null ) which will throw a NullReferenceException.Obeah
Please explain in what way this implementation is superior to simply setting myDlgHandler to null. The outcome at the end will be the same; the variable will have the value null. So why waste all the code and time with the call to GetInvocationList() and the loop? (Never mind the crashing bug.)Africa
@Martin, Yes, I missed that. I removed that alternate approachDark
@Peter, this approach allows the code to support conditional removal of some of the delegates, and it leaves the parent delegate in place for assignment of other, different, delegates. In this case (the OPs scenario), he wants the Avatar to enter a different room. So the delegate will be used, immediately, for another different room. The code could even be written to add the events for the new room, and then delete those for all rooms not the same as the currently occupied room.Dark
"this approach allows the code to support conditional removal of some of the delegates" -- based on what criteria? There's nothing in your code example that illustrates how that might be done, nor is there a practical means to do that. "it leaves the parent delegate in place" -- not sure what that even means. What's a "parent delegate"? The code you posted will literally have the net effect of setting myDlgHandler to null. What's left "in place"?Africa
"The code could even be written to add the events for the new room, and then delete those for all rooms not the same as the currently occupied room" -- it could, I guess. But that'd be a waste of time. Just setting the current value to null and then adding "the events for the new room" would have the same net effect, but do it more simply and efficiently.Africa
@Peter, what criteria? why, any criteria you want! "how that might be done? why, using an if() statement of course. if(Boolean) statements allow you to write code that only executes based on a stated conditional criterion, whose value is passed to the boolean argument of the if(bool argument) statement. "The code you posted will literally have the net effect of setting myDlgHandler to null" No it will not, it will be an empty delegate.Dark
"any criteria you want" -- you are missing the point. You claim a benefit, but don't demonstrate it. And whatever criteria you might have, removal of such delegates is not in any way enhanced by the example you've given. Feel free to demonstrate otherwise, but whatever demonstration you come up with, there will be a simpler version that doesn't involve calling GetInvocationList(). "No it will not, it will be an empty delegate" -- an "empty delegate" is null. Try it.Africa
Thank you so much! :DStaford
C
5

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.

When the avatar leaves the room, simply loop through the list of delegates performing a standard remove (-=).

Cicatrix answered 15/1, 2009 at 18:15 Comment(3)
True, but doesn't the framework store the delegates somewhere in a List of some type anyways?Ovule
Not sure about that... are you thinking of multicast delegates?Cicatrix
Yes, in the delegate itself Every delegate inherits from System.MultiCastDelegate, and incldues internal storage fopr an arbitrary number of delegates. When you "register a delegateHandler with a delegate, it just adds the new Hndlr to the list.Dark
G
4

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.

Gagliardi answered 15/1, 2009 at 18:6 Comment(2)
I don't think the example is complete, the question relates to multiple events. The answer being searched for is a magic "remove any delegates that I have that are attached to any of your events".Dogeared
@Anthony, the questioner updated the question after my answer. I'll update my responseGagliardi
M
4

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;   
}
Myelencephalon answered 15/1, 2009 at 18:48 Comment(1)
This is really no different than the accepted answer, and just as useless. Setting myEvent to null works just as well as removing individual handlers one by one.Africa
C
0

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.

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.

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

}

Clift answered 25/8, 2019 at 7:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.