При изучении этого вопроса мне стало интересно, как новые функции ковариации / контравариантности в C # 4.0 повлияют на него.
В бета-версии 1 C, похоже, не согласен с CLR. Вернитесь в C # 3.0, если у вас было:
public event EventHandler<ClickEventArgs> Click;
... а затем в другом месте у вас было:
button.Click += new EventHandler<EventArgs>(button_Click);
... компилятор прервется, потому что это несовместимые типы делегатов. Но в C # 4.0 он прекрасно компилируется, потому что в CLR 4.0 параметр типа теперь помечен как in
, так что это противоречиво, и поэтому компилятор предполагает, что многоадресный делегат +=
будет работать.
Вот мой тест:
public class ClickEventArgs : EventArgs { }
public class Button
{
public event EventHandler<ClickEventArgs> Click;
public void MouseDown()
{
Click(this, new ClickEventArgs());
}
}
class Program
{
static void Main(string[] args)
{
Button button = new Button();
button.Click += new EventHandler<ClickEventArgs>(button_Click);
button.Click += new EventHandler<EventArgs>(button_Click);
button.MouseDown();
}
static void button_Click(object s, EventArgs e)
{
Console.WriteLine("Button was clicked");
}
}
Но хотя он компилируется, он не работает во время выполнения (ArgumentException
: делегаты должны быть одного типа).
Это нормально, если вы добавляете только один из двух типов делегатов. Но комбинация двух разных типов в многоадресной рассылке вызывает исключение при добавлении второго.
Я полагаю, что это ошибка в CLR в бета-версии 1 (поведение компилятора выглядит, надеюсь, правильным).
Обновление для кандидата на выпуск:
Приведенный выше код больше не компилируется. Должно быть, откат TEventArgs
в типе делегата EventHandler<TEventArgs>
был отменен, поэтому теперь этот делегат имеет то же определение, что и в .NET 3.5.
То есть бета, на которую я смотрел, должна была иметь:
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
Теперь вернемся к:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Но параметр делегата Action<T>
T
все еще противоречив:
public delegate void Action<in T>(T obj);
То же самое для Func<T>
T
, являющегося ковариантным.
Этот компромисс имеет большой смысл, поскольку мы предполагаем, что основное использование делегатов многоадресной рассылки происходит в контексте событий. Я лично обнаружил, что никогда не использую многоадресные делегаты, кроме как в качестве событий.
Так что я думаю, что стандарты кодирования C # теперь могут принять новое правило: не формировать многоадресных делегатов из нескольких типов делегатов, связанных ковариацией / контравариантностью. И если вы не знаете, что это значит, просто избегайте использования Action
, чтобы события были в безопасности.
Конечно, этот вывод имеет значение для первоначального вопроса о том, что этот вопрос вырос из ...