在调查这个问题时,我很好奇 C# 4.0 中新的协变/逆变特性将如何影响它。
在 Beta 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
: Delegates 必须是相同的类型)。
如果您只添加两种委托类型中的任何一种,就可以了。但是多播中两种不同类型的组合在添加第二种时会导致异常。
我想这是 beta 1 中的 CLR 中的一个错误(编译器的行为看起来是正确的)。
发布候选更新:
上面的代码不再编译。一定是委托类型中的逆变性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
for 事件以确保安全。
当然,这个结论对最初的问题有影响,这个问题源于......