.NET 4.0中的协变量和逆变量错误

时间:2010-02-21 18:05:49

标签: c# delegates .net-4.0 covariance

C#4.0共同和逆变支持的一些奇怪行为:

using System;

class Program {
  static void Foo(object x) { }
  static void Main() {
    Action<string> action = _ => { };

    // C# 3.5 supports static co- and contravariant method groups
    // conversions to delegates types, so this is perfectly legal:
    action += Foo;

    // since C# 4.0 much better supports co- and contravariance
    // for interfaces and delegates, this is should be legal too:
    action += new Action<object>(Foo);
  }
}

这是ArgumentException: Delegates must be of the same type.

的结果 奇怪,不是吗?为什么Delegate.Combine()(在对代理执行+=操作时调用)为什么不支持运行时的协同和反演?

此外,我发现BCL的System.EventHandler<TEventArgs>委托类型在其通用TEventArgs参数上没有逆变注释!为什么?它是完全合法的,TEventArgs类型仅在输入位置使用。也许没有逆变注释,因为它很好地隐藏了Delegate.Combine()的错误?的;)

P.S。所有这些都会影响VS2010 RC及更高版本。

4 个答案:

答案 0 :(得分:38)

长话短说:关于方差,委托合并是所有混乱。我们在周期的后期发现了这一点。我们正在与CLR团队合作,看看我们是否能够提出一些方法来使所有常见方案在不破坏向后兼容性的情况下工作,等等,但无论我们提出什么,都可能无法进入4.0版本。希望我们能在一些服务包中整理出来。对于给您带来的不便,我深表歉意。

答案 1 :(得分:6)

协方差和逆变指定了泛型类型之间的继承关系。当你有协方差&amp;相反,类G<A>G<B>可能属于某种继承关系,具体取决于AB。调用泛型方法时,您可以从中受益。

但是,Delegate.Combine方法不是通用的,而documentation clearly says则会抛出异常:

  

ArgumentException - a b 都不是null引用(在Visual Basic中为Nothing), a b 不是同一委托类型的实例。

现在,Action<object>Action<string>肯定是不同委托类型的实例(即使通过继承关系相关),因此根据文档,它将引发异常。 Delegate.Combine方法可以支持这种情况听起来是合理的,但这只是一个可能的提议(显然直到现在才需要这样做,因为你不能声明继承的委托,所以在co / contra-variance之前,没有代表有任何继承关系)。

答案 2 :(得分:1)

委托组合的一个难点是,除非指定哪个操作数应该是子类型,哪个是超类型,否则不清楚结果应该是什么类型。可以编写一个包装器工厂,它将具有指定数量的参数和byval / byref模式的任何委托转换为超类型,但是使用相同的委托多次调用这样的工厂会产生不同的包装器(这可能会造成严重破坏)事件取消订阅)。或者可以创建一个Delegate.Combine版本,它将右侧委托强制转换为左侧委托的类型(作为奖励,返回不必是类型转换),但是必须编写一个特殊版本的委托。删除处理它。

答案 3 :(得分:0)

此解决方案最初由cdhowie发布,用于我的问题:Delegate conversion breaks equality and unables to disconnect from event但似乎解决了多播代理上下文中的协方差和逆变问题。

首先需要辅助方法:

public static class DelegateExtensions
{
    public static Delegate ConvertTo(this Delegate self, Type type)
    {
        if (type == null) { throw new ArgumentNullException("type"); }
        if (self == null) { return null; }

        if (self.GetType() == type)
            return self;

        return Delegate.Combine(
            self.GetInvocationList()
                .Select(i => Delegate.CreateDelegate(type, i.Target, i.Method))
                .ToArray());
    }

    public static T ConvertTo<T>(this Delegate self)
    {
        return (T)(object)self.ConvertTo(typeof(T));
    }
}

当你有代表时:

public delegate MyEventHandler<in T>(T arg);

您可以在组合代表时使用它,只需转换委托所需的类型:

MyEventHandler<MyClass> handler = null;
handler += new MyEventHandler<MyClass>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();
handler += new MyEventHandler<object>(c => Console.WriteLine(c)).ConvertTo<MyEventHandler<MyClass>>();

handler(new MyClass());

它还支持使用ConvertTo()方法以相同方式断开与事件的连接。 与使用某些自定义委托列表不同,此解决方案提供开箱即用的线程安全性。

完整代码包含一些示例,您可以在此处找到:http://ideone.com/O6YcdI