Как преобразовать делегат в идентичный делегат?
Существует два описания делегата:
во-первых, в сторонней сборке:
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. Это означает, что вы можете назначить делегатам не только методы с соответствующими сигнатурами, но также методы, возвращающие более производные типы (ковариация) или принимающие параметры, которые имеют менее производные типы (контравариантность), чем те, которые заданы типом делегирования. Это включает как общие, так и не общие делегаты.