调查时this question我很好奇 C# 4.0 中新的协变/逆变特性将如何影响它。
在 Beta 1 中,C# 似乎不同意 CLR。回到 C# 3.0,如果你有:
public event EventHandler<ClickEventArgs> Click;
...然后在其他地方你有:
button.Click += new EventHandler<EventArgs>(button_Click);
...编译器会呕吐,因为它们是不兼容的委托(delegate)类型。但在 C# 4.0 中,它编译得很好,因为在 CLR 4.0 中,类型参数现在标记为 in
。 ,所以它是逆变的,因此编译器假定多播委托(delegate) +=
将工作。
这是我的测试:
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
:委托(delegate)必须是同一类型)。
如果您只添加两种委托(delegate)类型中的一种也没关系。但是当添加第二个时,多播中两种不同类型的组合会导致异常。
我猜这是 beta 1 中 CLR 中的一个错误(编译器的行为看起来是正确的)。
候选发布更新:
以上代码不再编译。一定是 TEventArgs
的逆变在EventHandler<TEventArgs>
委托(delegate)类型已回滚,因此现在该委托(delegate)具有与 .NET 3.5 中相同的定义。
也就是我看的beta肯定有:
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
现在回到:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
但是 Action<T>
委托(delegate)参数 T
仍然是逆变的:
public delegate void Action<in T>(T obj);
Func<T>
也是如此的 T
是协变的。
只要我们假设多播委托(delegate)的主要用途是在事件上下文中,这种折衷就很有意义。我个人发现我从不使用多播委托(delegate),除非作为事件。
所以我想 C# 编码标准现在可以采用新规则:不要从通过协变/逆变相关的多个委托(delegate)类型形成多播委托(delegate)。如果您不知道那是什么意思,请避免使用 Action
。为了安全起见。
当然,这个结论对 the original question that this one grew from 有影响。 ...
最佳答案
非常有趣。您不需要使用事件来看到这种情况的发生,事实上我发现使用简单委托(delegate)更简单。
考虑 Func<string>
和 Func<object>
.在 C# 4.0 中,您可以隐式转换 Func<string>
至 Func<object>
因为您始终可以将字符串引用用作对象引用。但是,当您尝试将它们组合在一起时,事情就会出错。这是一个简短但完整的程序,以两种不同的方式演示了该问题:
using System;
class Program
{
static void Main(string[] args)
{
Func<string> stringFactory = () => "hello";
Func<object> objectFactory = () => new object();
Func<object> multi1 = stringFactory;
multi1 += objectFactory;
Func<object> multi2 = objectFactory;
multi2 += stringFactory;
}
}
这编译得很好,但是 Combine
调用(隐藏在 += 语法糖中)抛出异常。 (注释掉第一个以查看第二个。)
这绝对是个问题,虽然我不确定解决方案应该是什么。 在执行时委托(delegate)代码可能需要根据所涉及的委托(delegate)类型计算出最适合使用的类型。这有点恶心。如果有一个通用的 Delegate.Combine
就好了调用,但您无法真正以有意义的方式表达相关类型。
值得注意的一件事是协变转换是一个引用转换——在上面,multi1
和 stringFactory
引用同一个对象:不和写
Func<object> multi1 = new Func<object>(stringFactory);
(此时,下一行将无一异常(exception)地执行。)在执行时,BCL 确实必须处理 Func<string>
。和一个 Func<object>
合并;它没有其他信息可以继续。
这很讨厌,我真的希望它能以某种方式得到修复。我会提醒 Mads 和 Eric 这个问题,以便我们获得一些更明智的评论。
关于c# - .NET 4.0 和 C# 4.0 中的事件和委托(delegate)逆变,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1120688/