Using MulticastDelegate as parameter while avoiding DynamicInvoke
Asked Answered
F

3

3

I have a MulticastDelegate that can reference one of a number of (legacy) delegates that have the same signature. For example:

public delegate void ObjectCreated(object sender, EventArgs args);
public delegate void ObjectDeleted(object sender, EventArgs args);
//...

Those delegates are then used to define events:

public event ObjectCreated ObjectWasCreated;
public event ObjectDeleted ObjectWasDeleted;

I then have a method which takes in a MulticastDelegate that I use to do some common checking:

void DispatchEvent(MulticastDelegate handler, object sender, EventArgs args)
{
    if (handler != null)
    {
        // ...
        handler.DynamicInvoke(sender, args);
    }
}

Which is called from within other methods of the class wherein the events were defined:

DispatchEvent(ObjectWasCreated, sender, args);
DispatchEvent(ObjectWasDeleted, sender, args);

Is there a more concise way to do this that avoids DynamicInvoke?

Flogging answered 20/1, 2011 at 19:46 Comment(2)
Time for that legacy code to get an upgrade to EventHandler. Until then, no.Schreck
The real code isn't directly using EventArgs but is using a custom subclass. However, I can't see any reason it shouldn't use the same delegate for each of the dispatched events -- then I can changed from MulticastDelegate to the delegate type in question.Flogging
G
2

Here's my reflection-free solution. It basically implements a multicast delegate as a list. Less code? No. Better performance? I don't know. Cleaner? Meh.

public delegate void ObjectCreated(object sender, EventArgs args);
public delegate void ObjectDeleted(object sender, EventArgs args);

public event ObjectCreated ObjectWasCreated
{
    add
    {
        m_ObjectCreatedSubscribers.Add(value.Invoke);
    }
    remove
    {
        m_ObjectCreatedSubscribers.RemoveAll(e => e.Target.Equals(value));
    }
}
public event ObjectDeleted ObjectWasDeleted
{
    add
    {
        m_ObjectDeletedSubscribers.Add(value.Invoke);
    }
    remove
    {
        m_ObjectDeletedSubscribers.RemoveAll(e => e.Target.Equals(value));
    }
}

private List<Action<object, EventArgs>> m_ObjectCreatedSubscribers = new List<Action<object, EventArgs>>();
private List<Action<object, EventArgs>> m_ObjectDeletedSubscribers = new List<Action<object, EventArgs>>();

void DispatchEvent(List<Action<object, EventArgs>> subscribers, object sender, EventArgs args)
{
    foreach (var subscriber in subscribers)
        subscriber(sender, args);
}
Gramineous answered 20/1, 2011 at 21:19 Comment(2)
Well, it's not more concise but it avoids reflection and dynamic invoke and taught me something new, so thanks!Flogging
+1 for some innovation there, but does this help to dispatch individual events? I guess one can call DispatchEvent(ObjectWasCreated.Invoke)..Coward
C
1

One simple alternative is to use built in types like Action<,> or EventHandler instead of custom delegates, so that you get strong types.

public static event Action<object, EventArgs> ObjectWasCreated;
public static event Action<object, EventArgs> ObjectWasDeleted;  

void DispatchEvent(Action<object, EventArgs> handler, object sender, EventArgs args) 
{
    if (handler != null)
    {
        // ...
        handler(sender, args);
    }
}

or

public static event EventHandler ObjectWasCreated;
public static event EventHandler ObjectWasDeleted;  

void DispatchEvent(EventHandler handler, object sender, EventArgs args) 
{
    if (handler != null)
    {
        // ...
        handler(sender, args);
    }
}

Now your method call will be straightforward.

DispatchEvent(ObjectWasCreated, sender, args);
DispatchEvent(ObjectWasDeleted, sender, args);

But that's mostly not a good solution.

You could use dynamic, still much better than DynamicInvoke:

void DispatchEvent(MulticastDelegate handler, object sender, EventArgs args) 
{
    if (handler != null)
    {
        // ...
        ((dynamic)handler)(sender, args);
    }
}

Or may be generics:

void DispatchEvent<T>(T handler, object sender, EventArgs args) 
{
    if (handler != null)
    {
        // ...
        ((dynamic)handler)(sender, args);
    }
}

I did a small performance comparison and found dynamic to be too good actually:

For a million tries

MulticastDelegate + dynamic (first example) => 40 ms

generic + dynamic (second example) => 90 ms

MulticastDelegate + DynamicInvoke (given in question originally) => 940 ms

Coward answered 4/6, 2013 at 8:32 Comment(1)
Thanks for the ideas and performance metrics. I wouldn't have expected it to perform so well.Flogging
B
0

You could do something like:

void DispatchEvent(MulticastDelegate handler, object sender, EventArgs args)
{
    EventHandler eventHandler = 
        (EventHandler)Delegate.CreateDelegate(typeof(EventHandler), handler.GetType().GetMethod("Invoke"));

    eventHandler(sender, args);
}

I'm not sure if this'll be faster than using DynamicInvoke, though.

You'll have to use reflection somewhere. If each delegate could be guarenteed to only have one subscriber, then you could use the Delegate.Method property directly when creating the EventHandler, but as they're events, they're likely to have more than one subscriber...

Bucky answered 20/1, 2011 at 20:46 Comment(1)
-1. How is this helping OP? He doesn't have an EventHandler type, instead custom delegates.Coward

© 2022 - 2024 — McMap. All rights reserved.