Изучая этот вопрос, мне стало интересно, как это повлияет на новые возможности ковариации/контравариантности в С# 4.0.
В бета-версии 1 С#, похоже, не согласен с CLR. Вернитесь в С# 3.0, если у вас есть:
public event EventHandler<ClickEventArgs> Click;
... а затем в другом месте:
button.Click += new EventHandler<EventArgs>(button_Click);
... компилятор будет barf, потому что это несовместимые типы делегатов. Но в С# 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
является ковариантным.
Этот компромисс имеет большой смысл, если мы предполагаем, что первичное использование делегатов многоадресной рассылки находится в контексте событий. Я лично обнаружил, что я никогда не использую делегатов многоадресной рассылки, кроме как событий.
Итак, я полагаю, что стандарты кодирования С# теперь могут принять новое правило: не формируйте делегаты многоадресной передачи из нескольких типов делегатов, связанных с ковариацией/контравариантностью. И если вы не знаете, что это значит, просто избегайте использования Action
для событий, находящихся на безопасной стороне.
Конечно, этот вывод имеет значение для исходного вопроса, что этот росток вырос из...