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

Dan*_*ker 28 c# events delegates clr4.0 c#-4.0

在调查这个问题时,我很好奇C#4.0中新的协方差/逆变特性将如何影响它.

在Beta 1中,C#似乎不同意CLR.回到C#3.0,如果你有:

public event EventHandler<ClickEventArgs> Click;
Run Code Online (Sandbox Code Playgroud)

......然后你在其他地方:

button.Click += new EventHandler<EventArgs>(button_Click);
Run Code Online (Sandbox Code Playgroud)

...编译器会barf,因为它们是不兼容的委托类型.但是在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");
    }
}
Run Code Online (Sandbox Code Playgroud)

但是虽然它编译,但它在运行时不起作用(ArgumentException代理必须是相同的类型).

如果你只添加两种委托类型中的任何一种,那也没关系.但是,当添加第二个时,多播中两种不同类型的组合会导致异常.

我想这是beta 1中CLR中的一个错误(编译器的行为看起来很有希望).

候选发布更新:

上面的代码不再编译.它必须是的逆变TEventArgsEventHandler<TEventArgs>委托类型已回滚,所以现在该委托具有相同的定义,在.NET中3.5.

也就是说,我看过的测试版必须具备:

public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
Run Code Online (Sandbox Code Playgroud)

现在回到:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Run Code Online (Sandbox Code Playgroud)

Action<T>委托参数T仍然是逆变的:

public delegate void Action<in T>(T obj);
Run Code Online (Sandbox Code Playgroud)

这同样适用Func<T>T是协变的.

只要我们假设多播委托的主要用途是在事件的上下文中,这种妥协就很有意义.我个人发现除了作为事件之外,我从不使用多播代理.

所以我猜C#编码标准现在可以采用一个新规则:不要通过协方差/逆变来形成与多个代理类型相关的多播代理.如果您不知道这意味着什么,请避免使用Action事件来保证安全.

当然,这个结论对于这个问题的原始问题有影响......

Jon*_*eet 9

很有意思.您不需要使用事件来查看这种情况,事实上我发现使用简单的委托更简单.

考虑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;
    }    
}
Run Code Online (Sandbox Code Playgroud)

这编译很好,但两个Combine调用(由+ =语法糖隐藏)抛出异常.(注释第一个看第二个.)

这绝对是一个问题,虽然我不确定解决方案应该是什么.这有可能是在执行时委托代码需要制定出最合适的类型使用基于所涉及的委托类型.那有点讨厌.有一个通用Delegate.Combine调用会很好,但你无法以一种有意义的方式真正表达相关类型.

有一两件事值得一提的是,协变转换是引用转换-在上面,multi1stringFactory指向同一个对象:它一样的写作

Func<object> multi1 = new Func<object>(stringFactory);
Run Code Online (Sandbox Code Playgroud)

(此时,以下行将毫无例外地执行.)在执行时,BCL确实必须处理a Func<string>和a Func<object>组合; 它没有其他信息可以继续.

这很讨厌,我真的希望它能以某种方式得到修复.我会提醒Mads和Eric这个问题所以我们可以得到一些更明智的评论.


Wes*_*ser 0

您是否从两者中都得到了 ArgumentException ?如果异常仅由新处理程序引发,那么我认为它是向后兼容的。

顺便说一句,我认为你的评论混淆了。在 C# 3.0 中:

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

不会跑的。那是c#4.0