.NET 4.0和C#4.0中的事件和委托逆转

Event and delegate contravariance in .NET 4.0 and C# 4.0

在研究这个问题时,我很好奇C 4.0中的新协方差/反方差特性将如何影响它。

在beta 1中,c似乎不同意clr。回到C 3.0,如果你有:

1
public event EventHandler<ClickEventArgs> Click;

…然后在其他地方你有:

1
button.Click += new EventHandler<EventArgs>(button_Click);

…编译器会死机,因为它们是不兼容的委托类型。但是在C 4.0中,它编译得很好,因为在clr 4.0中,类型参数现在标记为in,因此它是反变量的,因此编译器假定多播委托+=可以工作。

这是我的测试:

2

但尽管它是编译的,但它在运行时不工作(ArgumentException:委托必须是同一类型)。

如果只添加这两种委托类型中的任何一种,也可以。但是多播中两种不同类型的组合会在添加第二种类型时导致异常。

我想这是测试版1的clr中的一个bug(编译器的行为看起来很有希望是正确的)。

发布候选的更新:

上述代码不再编译。必须是EventHandler委托类型中TEventArgs的反差已回滚,因此委托的定义与.NET 3.5中的定义相同。

也就是说,我看到的贝塔一定有:

1
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);

现在又回到:

4

Action委托参数T仍然是反变的:

1
public delegate void Action<in T>(T obj);

同样适用于FuncT是协变的。

只要我们假定多播委托的主要用途是在事件上下文中,这种折衷就非常有意义。我个人发现,除了作为事件之外,我从不使用多播代理。

所以我想C编码标准现在可以采用一个新的规则:不要从多个通过协方差/反方差相关的委托类型中形成多播委托。如果你不知道这意味着什么,那就避免使用Action,因为事件是安全的。

当然,这个结论对这个问题的起源有一定的启示。


非常有趣。您不需要使用事件来看到这种情况的发生,实际上,我发现使用简单的委托更简单。

FuncFunc为例。在C 4.0中,您可以隐式地将Func转换为Func,因为您可以始终使用字符串引用作为对象引用。然而,当你试图把它们结合起来时,事情就会出错。下面是一个简短但完整的程序,它以两种不同的方式演示了这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.Combine调用是很好的,但是您不能真正以有意义的方式表达相关类型。

值得注意的一点是,协变转换是一个引用转换——在上面,multi1stringFactory指的是同一个对象:它与书写不同。

1
Func<object> multi1 = new Func<object>(stringFactory);

(此时,下一行将无一例外地执行)在执行时,BCL确实必须处理FuncFunc的组合;它没有其他信息可以继续。

它很讨厌,我真的希望它能以某种方式得到修复。我会提醒马兹和埃里克这个问题,这样我们可以得到更多的信息评论。


我只需要在我的申请中解决这个问题。我做了以下工作:

2

我不知道常规的多播事件是否存在相关差异。就我而言,它是有效的…

顺便说一下:我从来都不喜欢C中的活动。我不明白为什么有语言特性,当它不提供任何优势时。


你是否从两者中都得到了争论例外?如果只由新的处理程序抛出异常,那么我认为它是向后兼容的。

顺便说一句,我想你的意见有点混乱。在C 3中:

button.Click += new EventHandler(button_Click); // old

不会跑的。那是C 4