Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove duplicate delegate from event

Tags:

c#

I would like to remoe duplicate delegates from an event. So I have written the following code. It works fine. My application is a time critcal application. Is there any other optimized mechansim to achieve the same. Please help me


public void FireEvent()
{
    Dictionary<Delegate, Delegate> dic = new Dictionary<Delegate, Delegate>();

    Delegate[] m = this.Paused.GetInvocationList();
    foreach (Delegate d in m)
    {
        Delegate dout;
        if (dic.TryGetValue(d, out dout))
        {                    
            continue;
        } 
        else
        {
            dic.Add(d, d);
        }
        d.DynamicInvoke(new object[2] { this, null });
    }
}
like image 228
Maanu Avatar asked Aug 19 '10 03:08

Maanu


1 Answers

Problem with original approach

If this is really a time critical application, I would strongly advise changing your code.

  1. You construct and populate a new Dictionary<Delegate, Delegate> on every method call. This is quite wasteful.
  2. You use DynamicInvoke, which has slower performance than regular invocation to begin with.
  3. You construct a new object[] to pass as a parameter to your DynamicInvoke call, again on every FireEvent call.

This is a bit of a corruption of the established mechanism for event handling.

Suggestion for improved approach

Here's a much better solution, in my opinion: instead of having this FireEvent method which bends over backwards to ignore duplicate delegates that have been added, why not just prevent delegates from being attached to the event multiple times in the first place?

private HashSet<EventHandler> _pausedHandlers = new HashSet<EventHandler>();

public event EventHandler Paused
{
    add // will not add duplicates
    { _pausedHandlers.Add(value); }

    remove
    { _pausedHandlers.Remove(value); }
}

Then you can simply raise the event in the much more conventional, time-tested way, confident that no delegates have been attached to the event more than once.

protected void OnPaused()
{
    foreach (EventHandler handler in _pausedHandlers)
    {
        try
        {
            handler(this, EventArgs.Empty);
        }
        catch
        {
            // up to you what to do here
        }
    }
}

Note on the concept of "duplicate delegates"

The comments to this answer have shed some light on the issue of delegate equality that I felt it would be beneficial to include in this answer. If you're interested, take a look at the following code example I wrote in an attempt to make this topic a little bit easier to understand.

class Program
{
    static void Main(string[] args)
    {
        // Even though the code for FirstHandler and SecondHandler is the same,
        // they will not (nor should they) be considered equal for the purpose
        // of detecting duplicates.
        EventHandler handler1 = FirstHandler;
        EventHandler handler2 = SecondHandler;

        // Since handler2 and handler3 point to the same method, on the other
        // hand, they will (and ought to) be treated as equal.
        EventHandler handler3 = SecondHandler;

        // So this prints 'False'...
        Console.WriteLine(handler1.Equals(handler2));

        // ...and this prints 'True'.
        Console.WriteLine(handler2.Equals(handler3));

        // Now take a look at the code for SetAnonymousHandler below. The
        // method accepts an EventHandler by reference and compares it for
        // equality to a delegate pointing to an anoymous lambda method. If the
        // two are equal, it sets the EventHandler to this new delegate and 
        // returns true; otherwise it returns false.

        // This prints 'True', as handler1 is not equal to the delegate
        // declared within the SetAnonymousHandler method.
        Console.WriteLine(SetAnonymousHandler(ref handler1));

        // HOWEVER, now it prints 'False' for a simple reason: the delegate 
        // declared in SetAnonymousHandler points to an actual method. The fact
        // that it is an anonymous method simply means that the compiler
        // automatically generates a "proper" method for it in IL (to see what
        // I mean, read the comment at the bottom of this class). So now,
        // as with handler2 and handler3 above, handler1 and the delegate
        // declared in SetAnonymousHandler point to the same method and are
        // therefore equal; the method returns false.
        Console.WriteLine(SetAnonymousHandler(ref handler1));

        Console.ReadLine();
    }

    static void FirstHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Testing");
    }

    static void SecondHandler(object sender, EventArgs e)
    {
        Console.WriteLine("Testing");
    }

    static bool SetAnonymousHandler(ref EventHandler handler)
    {
        EventHandler anonymous = (sender, e) => Console.WriteLine("Testing");

        if (!handler.Equals(anonymous))
        {
            handler = anonymous;
            return true;
        }
        else
        {
            return false;
        }
    }

    // Note: When the above method is compiled, the C# compiler generates code
    // that would look something like this if translated back from IL to C#
    // (not exactly, but the point is just to illustrate that an anoymous
    // method, after compilation, is really just like a "proper"
    // -- i.e., non-anonymous -- method).

    //static bool SetAnonymousHandler(ref EventHandler handler)
    //{
    //    EventHandler anonymous = SetAnonymousHandler_Anonymous;

    //    if (handler.Equals(anonymous))
    //    {
    //        handler = anonymous;
    //        return true;
    //    }
    //    else
    //    {
    //        return false;
    //    }
    //}

    //static void SetAnonymousHandler_Anonymous(object sender, EventArgs e)
    //{
    //    Console.WriteLine("Testing");
    //}
}
like image 119
Dan Tao Avatar answered Sep 29 '22 04:09

Dan Tao