.NET 4.0中的合作和变体错误

一些奇怪的行为与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.的结果ArgumentException: Delegates must be of the same type.

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

此外,我发现BCL的System.EventHandler<TEventArgs>委托types没有对它的通用TEventArgs参数的逆变注解! 为什么? 这是完全合法的, TEventArgstypes只用于input位置。 也许有没有逆变的注释,因为它很好地隐藏与Delegate.Combine()的错误? ;)

ps所有这些影响VS2010 RC和更高版本。

长话短说:委托合并在方差方面都是混乱的 。 我们在周期的晚些时候发现了这个。 我们正在与CLR团队合作,看看我们是否可以想出一些方法来使所有的常见场景都能正常工作,而不会破坏向后兼容性等等,但无论我们想出什么东西,都可能无法进入4.0版本。 希望我们能够在一些服务包中完成整理。 我带来的不便表示歉意。

协变和逆变指定了generics之间的inheritance关系。 当你有协变和逆变时,根据AB是什么,类G<A>G<B>可能处于某种inheritance关系。 调用generics方法时可以从中受益。

但是, Delegate.Combine方法不是通用的, 文档清楚地说明了何时抛出exception:

ArgumentExceptionab都不是null引用(在Visual Basic中为Nothing ),而ab不是同一个委托types的实例。

现在, Action<object>Action<string>当然是不同委托types的实例(即使通过inheritance关系相关),所以根据文档,它会抛出一个exception。 Delegate.Combine方法可以支持这种情况,这听起来是合理的,但这只是一个可能的build议(显然这是不需要的,直到现在,因为你不能声明inheritance委托,所以在共同/反对之前,没有委托有任何inheritance关系)。

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

这个解决scheme最初是由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)); } } 

当你有一个代表:

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

你可以在组合委托时使用它,只需转换一个委托types即可:

 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()方法以相同的方式断开事件。 与使用一些自定义代表列表不同,此解决scheme提供了线程安全性。

完整的代码,你可以在这里find一些示例: http : //ideone.com/O6YcdI