C# .NET4.0中的协方差和逆变错误

C# .NET4.0中的协方差和逆变错误,c#,delegates,.net-4.0,covariance,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 delega

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);
  }
}
使用系统;
班级计划{
静态void Foo(对象x){}
静态void Main(){
动作动作==>{};
//C#3.5支持静态协变和逆变方法组
//转换为委托类型,因此这是完全合法的:
动作+=Foo;
//因为C#4.0更好地支持协变和逆变
//对于接口和委托,这也是合法的:
行动+=新行动(Foo);
}
}
它的结果带有
ArgumentException:委托必须是同一类型。

奇怪,不是吗?为什么
Delegate.Combine()
(在代理上执行
+=
操作时调用)在运行时不支持协变和逆变

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


p、 所有这些都会影响VS2010 RC和更高版本。

协方差和逆变指定泛型类型之间的继承关系。当您有协方差和逆变时,将引发异常的类
G:

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


现在,
Action
Action
肯定是不同委托类型的实例(即使通过继承关系相关),因此根据文档,它将抛出异常。
Delegate.Combine
方法可以支持此方案,这听起来很合理,但这只是一个可能的方案(显然,到目前为止还不需要这样做,因为您无法声明继承的委托,所以在协方差/逆变换之前,没有委托具有任何继承关系)

长话短说:就差异而言,代表组合完全是一团糟。我们在周期的后期发现了这一点。我们正在与CLR团队合作,看看我们是否能想出一些方法,在不破坏向后兼容性的情况下使所有常见场景都能工作,等等,但是我们想出的任何方法都可能无法进入4.0版本。希望我们能在一些服务包中解决所有问题。对于给您带来的不便,我深表歉意。

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

这个解决方案最初是由cdhowie为我的问题发布的:但它似乎解决了多播代理上下文中的协方差和逆变问题

首先需要一个助手方法:

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));
    }
}
公共静态类DelegateExtensions
{
公共静态委托ConvertTo(此委托自身,类型)
{
如果(type==null){抛出新的ArgumentNullException(“type”);}
如果(self==null){returnnull;}
if(self.GetType()==type)
回归自我;
返回委托。合并(
self.GetInvocationList()
.Select(i=>Delegate.CreateDelegate(类型、i.Target、i.Method))
.ToArray());
}
公共静态T转换器(此委托自身)
{
返回(T)(object)self.ConvertTo(typeof(T));
}
}
当您有代理人时:

public delegate MyEventHandler<in T>(T arg);
公共委托MyEventHandler(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());
MyEventHandler处理程序=null;
handler+=newmyeventhandler(c=>Console.WriteLine(c)).ConvertTo();
handler+=newmyeventhandler(c=>Console.WriteLine(c)).ConvertTo();
处理程序(新的MyClass());
它还支持使用
ConvertTo()
方法以相同的方式断开与事件的连接。 与使用某些自定义委托列表不同,此解决方案提供了开箱即用的线程安全性


用您可以在这里找到的一些示例完成代码:

是的,我对文档和
委托的定义是正确的。。。但是关于支持这个场景:为什么您要谈论代理继承?对于我的场景,泛型参数应该具有继承关系,而不是委托类型本身。例如,使用
EventHandler
委托实例订阅
EventHandler。更准确地说,我应该谈论子类型(即,当您可以将
A
类型的值视为
B
类型的值时)。协方差和逆变定义泛型委托之间的子类型关系(基于其类型参数之间的子类型/继承关系)。可以将任何子类型(继承的)转换为超类型(基或接口),委托之间的协方差使用相同的转换规则。然而,这并没有扩展到
Delegate.Combine
,它可能使用给定的委托的内部结构作为参数。这甚至是在
Delegate.Combine(Delegate,Delegate)的签名发挥作用之前记录的