Изучая этот вопрос, мне стало любопытно, как на него повлияют новые функции ковариации / контравариантности в 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);
То же самое касается T Func<T> ковариантности.
Этот компромисс имеет большой смысл, если мы предполагаем, что основное использование делегатов многоадресной рассылки находится в контексте событий. Я лично обнаружил, что никогда не использую многоадресные делегаты, кроме как события.
Итак, я полагаю, что стандарты кодирования C # теперь могут принять новое правило: не формировать многоадресные делегаты из нескольких типов делегатов, связанных ковариацией / контравариантностью. И если вы не знаете, что это значит, просто избегайте использования Action для событий, чтобы быть в безопасности.
Конечно, этот вывод имеет значение для исходного вопроса, из которого возник этот вопрос ...