c# - .NET 4.0 和 C# 4.0 中的事件和委托(delegate)逆变

标签 c# events delegates c#-4.0 clr4.0

调查时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 就好了调用,但您无法真正以有意义的方式表达相关类型。

值得注意的一件事是协变转换是一个引用转换——在上面,multi1stringFactory引用同一个对象:和写

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/

相关文章:

c# - 不带\0027 将字符串序列化为 JSON

C#:通用参数与对象类型参数

javascript - 如何将 jquery 对象传递给 "On"方法(第二个参数)?

javascript - 如果不在 onLoad 函数中,jQuery 事件处理程序将不起作用

ios - 如何缩小 iOS 中的 View Controller ?

c# - 在ASP.NET Web应用程序中记录错误

c# - 如何切换到特定文档的处理

c# - C#中的事件数组?

ios - swift 将 View 设置为自己的委托(delegate)

c# - 带有事件处理程序的 VB.NET 到 C#