Как преобразовать делегат в идентичный делегат?

Существует два описания делегата: во-первых, в сторонней сборке:

public delegate void ClickMenuItem (object sender, EventArgs e)

второй, стандарт:

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

Я пытаюсь написать метод, который получит параметр типа EventHandler и вызовет стороннюю библиотеку с параметром ClickMenuItem.

Как преобразовать ClickMenuItem в EventHandler?

Ответы

Ответ 1

К счастью, это просто. Вы можете просто написать:

ClickMenuItem clickMenuItem = ...; // Wherever you get this from
EventHandler handler = new EventHandler(clickMenuItem);

И наоборот:

EventHandler handler = ...;
ClickMenuItem clickMenuItem = new ClickMenuItem(handler);

Это будет работать даже в С# 1.0. Обратите внимание, что если вы измените значение исходной переменной, это изменение не будет отражено в "конвертированном". Например:

ClickMenuItem click = new ClickMenuItem(SomeMethod);
EventHandler handler = new EventHandler(click);
click = null;

handler(this, EventArgs.Empty); // This will still call SomeMethod

Ответ 2

EDIT: существует четвертый вариант, т.е. избежать всей этой глупости и сделать то, что предлагает Джон Скит в своем ответе.

Что-то вроде этого?

public static EventHandler ToEventHandler(this ClickMenuItem clickMenuItem)
{
    if (clickMenuItem == null)
        return null;

   return (sender, e) => clickMenuItem(sender, e);
}

и наоборот:

public static ClickMenuItem ToClickMenuItem(this EventHandler eventHandler)
{
   if (eventHandler == null)
       return null;

   return (sender, e) => eventHandler(sender, e);
}

Обратите внимание, что компилятор указывает, какие типы делегатов преобразуют выражения lamda.

EDIT: Если вы предпочитаете, вы также можете использовать анонимных делегатов.

EventHandler eventHandler =  delegate(object sender, EventArgs e)
                             { 
                                clickMenuItem(sender, e); 
                             };
return eventHandler; // can be inlined, type-inference works fine

Третий вариант, конечно, состоит в том, чтобы написать сам класс закрытия. Я бы не рекомендовал этого, но он дает вам представление о том, что делает компилятор с анонимными методами. Что-то вроде:

public static class ClickMenuItemExtensions
{
    public static EventHandler ToEventHandler(this ClickMenuItem clickMenuItem)
    {
        if (clickMenuItem == null)
            return null;

        // new EventHandler not required, included only for clarity 
        return new EventHandler(new Closure(clickMenuItem).Invoke);
    }

    private sealed class Closure
    {
        private readonly ClickMenuItem _clickMenuItem;

        public Closure(ClickMenuItem clickMenuItem)
        {
            _clickMenuItem = clickMenuItem;
        }

        public void Invoke(object sender, EventArgs e)
        {
            _clickMenuItem(sender, e);
        }
    }
}

Ответ 3

В дополнение к другим ответам, если вы хотите сделать преобразование между совместимыми типами делегатов, не зная тип во время компиляции, вы можете сделать что-то вроде этого:

static Delegate ConvertDelegate(Delegate sourceDelegate, Type targetType)
{
    return Delegate.CreateDelegate(
            targetType,
            sourceDelegate.Target,
            sourceDelegate.Method);
}

Это может быть полезно, если вам нужно динамически подписываться на событие.

Ответ 4

Ответ Томаса Левеска не подходит для некоторых особых случаев. Это улучшенная версия.

public static Delegate ConvertDelegate(this Delegate src, Type targetType, bool doTypeCheck)
{
    //Is it null or of the same type as the target?
    if (src == null || src.GetType() == targetType)
        return src;
    //Is it multiple cast?
    return src.GetInvocationList().Count() == 1
        ? Delegate.CreateDelegate(targetType, src.Target, src.Method, doTypeCheck)
        : src.GetInvocationList().Aggregate<Delegate, Delegate>
            (null, (current, d) => Delegate.Combine(current, ConvertDelegate(d, targetType, doTypeCheck)));
}

Преимущество вышеуказанного кода заключается в том, что он проходит следующий тест

EventHandler e = (o,e)=>{}
var a = e.ConvertDelegate(typeof(Action<object, EventArgs>), true);
Assert.AreEqual(e, e.ConvertDelegate(typeof(EventHandler), true));

в то время как

EventHandler e = (o,e)=>{}
var a = new Action<object, EventArgs>(e);
Assert.AreEqual(e, new EventHandler(a));

не удастся.

Ответ 5

Вам может потребоваться проверка Отклонение в делегатах.

.NET Framework 3.5 и Visual Studio 2008 внедрили поддержку дисперсии для сопоставления сигнатур меток с типами делегатов во всех делегатах на С# и Visual Basic. Это означает, что вы можете назначить делегатам не только методы с соответствующими сигнатурами, но также методы, возвращающие более производные типы (ковариация) или принимающие параметры, которые имеют менее производные типы (контравариантность), чем те, которые заданы типом делегирования. Это включает как общие, так и не общие делегаты.