Как преобразовать делегата в идентичного делегата? - PullRequest
17 голосов
/ 28 октября 2010

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

public delegate void ClickMenuItem (object sender, EventArgs e)

секунда, стандарт:

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

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

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

Ответы [ 5 ]

26 голосов
/ 28 октября 2010

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

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

И наоборот:

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

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

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

handler(this, EventArgs.Empty); // This will still call SomeMethod
6 голосов
/ 28 октября 2010

РЕДАКТИРОВАТЬ: есть четвертый вариант, то есть, чтобы избежать всей этой чепухи и делать то, что предлагает Джон Скит в своем ответе.

Как то так?

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

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

РЕДАКТИРОВАТЬ: Если вы предпочитаете, вы также можете использовать анонимных делегатов.

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);
        }
    }
}
5 голосов
/ 28 октября 2010

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

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

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

1 голос
/ 16 марта 2015

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

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

не удастся.1010 *

0 голосов
/ 28 октября 2010

Вы можете оформить заказ Дисперсия в делегатах .

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

...