.NET 4.0 和 C# 4.0 中的事件和委托逆变 [英] Event and delegate contravariance in .NET 4.0 and C# 4.0

查看:16
本文介绍了.NET 4.0 和 C# 4.0 中的事件和委托逆变的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

公共事件EventHandler点击;

...然后在其他地方:

button.Click += new EventHandler(button_Click);

... 编译器会报错,因为它们是不兼容的委托类型.但是在 C# 4.0 中,它编译得很好,因为在 CLR 4.0 中,类型参数现在被标记为 in,因此它是逆变的,因此编译器假定多播委托 += 会起作用.

这是我的测试:

public class ClickEventArgs : EventArgs { }公共类按钮{公共事件 EventHandler点击;公共无效鼠标按下(){Click(this, new ClickEventArgs());}}课程计划{静态无效主(字符串 [] args){按钮按钮 = new Button();button.Click += new EventHandler(button_Click);button.Click += new EventHandler(button_Click);button.MouseDown();}static void button_Click(object s, EventArgs e){Console.WriteLine("按钮被点击");}}

但是虽然可以编译,但在运行时不起作用(ArgumentException:Delegates must be the same type).

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

我猜这是测试版 1 中 CLR 中的一个错误(编译器的行为看起来是正确的).

候选版本更新:

以上代码不再编译.肯定是 EventHandler 委托类型中 TEventArgs 的逆变已经回滚了,所以现在该委托的定义与 .NET 3.5 中的相同.

也就是说,我看过的测试版肯定有:

public delegate void EventHandler(object sender, TEventArgs e);

现在又回到了:

public delegate void EventHandler(object sender, TEventArgs e);

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

public delegate void Action(T obj);

同样适用于 FuncT 是协变的.

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

所以我猜 C# 编码标准现在可以采用一个新规则:不要从通过协变/逆变相关的多个委托类型形成多播委托.如果你不知道这意味着什么,为了安全起见,请避免使用 Action 来处理事件.

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

解决方案

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

考虑 FuncFunc.在 C# 4.0 中,您可以将 Func 隐式转换为 Func,因为您始终可以使用字符串引用作为对象引用.但是,当您尝试将它们组合起来时,事情就会出错.这是一个简短但完整的程序,以两种不同的方式演示了该问题:

使用系统;课程计划{静态无效主(字符串 [] args){函数<字符串>stringFactory = () =>你好";Func<对象>objectFactory = () =>新对象();Func<对象>multi1 = 字符串工厂;multi1 += objectFactory;Func<对象>multi2 = 对象工厂;multi2 += stringFactory;}}

这编译得很好,但是两个 Combine 调用(被 += 语法糖隐藏)都抛出异常.(注释掉第一个可以看到第二个.)

这绝对是一个问题,尽管我不确定解决方案应该是什么.在执行时,委托代码可能需要根据所涉及的委托类型找出最合适的类型来使用.这有点恶心.有一个通用的 Delegate.Combine 调用会非常好,但您无法真正以有意义的方式表达相关类型.

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

Funcmulti1 = new Func(stringFactory);

(此时,下一行将毫无例外地执行.)在执行时,BCL 确实必须处理一个 Func 和一个 Func; 被合并;它没有其他信息可以继续.

这很糟糕,我真的希望它以某种方式得到修复.我会提醒 Mads 和 Eric 这个问题,以便我们获得更明智的评论.

While investigating this question I got curious about how the new covariance/contravariance features in C# 4.0 will affect it.

In Beta 1, C# seems to disagree with the CLR. Back in C# 3.0, if you had:

public event EventHandler<ClickEventArgs> Click;

... and then elsewhere you had:

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

... the compiler would barf because they're incompatible delegate types. But in C# 4.0, it compiles fine, because in CLR 4.0 the type parameter is now marked as in, so it is contravariant, and so the compiler assumes the multicast delegate += will work.

Here's my test:

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");
    }
}

But although it compiles, it doesn't work at runtime (ArgumentException: Delegates must be of the same type).

It's okay if you only add either one of the two delegate types. But the combination of two different types in a multicast causes the exception when the second one is added.

I guess this is a bug in the CLR in beta 1 (the compiler's behaviour looks hopefully right).

Update for Release Candidate:

The above code no longer compiles. It must be that the contravariance of TEventArgs in the EventHandler<TEventArgs> delegate type has been rolled back, so now that delegate has the same definition as in .NET 3.5.

That is, the beta I looked at must have had:

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

Now it's back to:

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

But the Action<T> delegate parameter T is still contravariant:

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

The same goes for Func<T>'s T being covariant.

This compromise makes a lot of sense, as long as we assume that the primary use of multicast delegates is in the context of events. I've personally found that I never use multicast delegates except as events.

So I guess C# coding standards can now adopt a new rule: don't form multicast delegates from multiple delegate types related through covariance/contravariance. And if you don't know what that means, just avoid using Action for events to be on the safe side.

Of course, that conclusion has implications for the original question that this one grew from...

解决方案

Very interesting. You don't need to use events to see this happening, and indeed I find it simpler to use simple delegates.

Consider Func<string> and Func<object>. In C# 4.0 you can implicitly convert a Func<string> to Func<object> because you can always use a string reference as an object reference. However, things go wrong when you try to combine them. Here's a short but complete program demonstrating the problem in two different ways:

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;
    }    
}

This compiles fine, but both of the Combine calls (hidden by the += syntactic sugar) throw exceptions. (Comment out the first one to see the second one.)

This is definitely a problem, although I'm not exactly sure what the solution should be. It's possible that at execution time the delegate code will need to work out the most appropriate type to use based on the delegate types involved. That's a bit nasty. It would be quite nice to have a generic Delegate.Combine call, but you couldn't really express the relevant types in a meaningful way.

One thing that's worth noting is that the covariant conversion is a reference conversion - in the above, multi1 and stringFactory refer to the same object: it's not the same as writing

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

(At that point, the following line will execute with no exception.) At execution time, the BCL really does have to deal with a Func<string> and a Func<object> being combined; it has no other information to go on.

It's nasty, and I seriously hope it gets fixed in some way. I'll alert Mads and Eric to this question so we can get some more informed commentary.

这篇关于.NET 4.0 和 C# 4.0 中的事件和委托逆变的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆