Использование MulticastDelegate в качестве параметра, избегая DynamicInvoke

У меня есть MulticastDelegate который может ссылаться на одного из (унаследованных) делегатов с одинаковой подписью. Например:

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

Затем эти делегаты используются для определения событий:

public event ObjectCreated ObjectWasCreated;
public event ObjectDeleted ObjectWasDeleted;

Затем у меня есть метод, который принимает MulticastDelegate что я использую, чтобы сделать некоторые общие проверки:

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

Который вызывается из других методов класса, в котором были определены события:

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

Есть ли более краткий способ сделать это, чтобы избежать DynamicInvoke?

3 ответа

Решение

Вот мое решение без отражения. Он в основном реализует многоадресный делегат в виде списка. Меньше кода? Нет. Лучшая производительность? Я не знаю. Очиститель? Мех.

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

Одной простой альтернативой является использование встроенных типов, таких как Action<,> или же EventHandler вместо пользовательских делегатов, так что вы получите сильные типы.

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

или же

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

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

Теперь ваш вызов метода будет простым.

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

Но это в основном не очень хорошее решение.

Вы могли бы использовать dynamicвсе еще намного лучше чем DynamicInvoke:

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

Или могут быть дженерики:

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

Я сделал небольшое сравнение производительности и нашел dynamic быть слишком хорошим на самом деле:

За миллион попыток

MulticastDelegate + dynamic (первый пример) => 40 мс

generic + dynamic (второй пример) => 90 мс

MulticastDelegate + DynamicInvoke (задано изначально) => 940 мс

Вы могли бы сделать что-то вроде:

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

    eventHandler(sender, args);
}

Однако я не уверен, что это будет быстрее, чем использование DynamicInvoke.

Вам придется где-то использовать отражение. Если можно гарантировать, что у каждого делегата будет только один подписчик, вы можете использовать свойство Delegate.Method непосредственно при создании EventHandler, но поскольку они являются событиями, у них может быть более одного подписчика...

Другие вопросы по тегам