C# .NET4.0中的协方差和逆变错误
C#4.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
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)的签名发挥作用之前记录的