Querying one delegate twice for one event

2019-05-22 11:15发布

问题:

I was recently asked a question what will happen if I would query one handler twice. Let me just show you code:

 public delegate void OpenEventHandler(object sender, EventArgs e);

 public class MyWindow
 {
     public event OpenEventHandler Open;

     public void OpenWindow()
     {
         if (Open != null)
         {
             Open(this, new EventArgs());
         }
     }
 }

 public class TwoDelegates
 {
     public static void HandleOpen(Object sender, EventArgs e)
     {
         Console.WriteLine("Birds fly");
         (sender as MyWindow).Open -= HandleOpen;
     }

     public static void Run()
     {
         var window = new MyWindow();
         window.Open += HandleOpen;
         window.Open += HandleOpen;

         window.OpenWindow();
         Console.ReadKey();
     }
 }

I wonder why string is still printed twice. On the beginning of it Invocation list consist of two items with same delegate reference, but after a first run it is cleaned up, still the secon invocaiton appears.

Update1:

Seems that even simple -= removes only one entry:

 var window = new MyWindow();

 window.Open += HandleOpen;
 window.Open += HandleOpen;

 Console.WriteLine(window.getHandlers().Count());
 window.Open -= HandleOpen;
 Console.WriteLine(window.getHandlers().Count());

Though debug mode in VS2010, while you look through window.Open internal properties shows empty invocation list with 0 count. Seems that it's some magic in debug info displayed in VS.

回答1:

It's a matter of how the delegate fires the event handlers. It takes a copy of the internal delegate list before it begins firing it off. As a result, adding or removing event handlers for an event inside of an event handler for that same event only affects future invocations of that event, not the current invocation.



回答2:

To expand on what Servy said, here's a slightly modified version of your code, with some debugging helpers to clarify what's going on. In the HandleOpen function, we examine the event handlers before and after removing HandleOpen from the event. The summary is: There's two copies of HandleOpen in your multicast, there's no reason for a single Open-=HandleOpen to remove both of them.

public class MyWindow
{
    public event OpenEventHandler Open;

    public void OpenWindow()
    {
        if (Open != null)
        {
            Open(this, new EventArgs());
        }
    }

    public Delegate[] getHandlers()
    {
        return Open.GetInvocationList();
    }
}

public class TwoDelegates
{
    public static void HandleOpen(Object sender, EventArgs e)
    {
        Console.WriteLine("Birds fly");
        var thisWin = sender as MyWindow;
        var before = thisWin.getHandlers();

        (sender as MyWindow).Open -= HandleOpen;
        var after = thisWin.getHandlers();

    }

    public static void Run()
    {
        var window = new MyWindow();
        window.Open += HandleOpen;
        window.Open += HandleOpen;

        window.OpenWindow();
        Console.ReadKey();
    }
}