36

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()? ;)

ps 所有这些都会影响到VS2010 RC 及更高版本。

4

4 回答 4

38

长话短说:代表组合在方差方面都搞砸了。我们在周期的后期发现了这一点。我们正在与 CLR 团队合作,看看我们是否可以想出一些方法来使所有常见场景都工作而不会破坏向后兼容性等等,但是我们想出的任何东西都可能不会进入 4.0 版本。希望我们能在一些服务包中解决所有问题。我带来的不便表示歉意。

于 2010-02-21T23:38:56.003 回答
6

协变和逆变指定泛型类型之间的继承关系。当你有协变和逆变时,类G<A>G<B>可能处于某种继承关系,这取决于什么AB是。调用泛型方法时,您可以从中受益。

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

ArgumentException- ab都不是null引用(Nothing在 Visual Basic 中),并且ab不是相同委托类型的实例。

现在,Action<object>并且Action<string>肯定是不同委托类型的实例(即使通过继承关系相关),所以根据文档,它会抛出异常。该Delegate.Combine方法可以支持这种情况听起来很合理,但这只是一个可能的提议(显然直到现在才需要这样做,因为您不能声明继承的委托,所以在协/逆变之前,没有委托有任何继承关系)。

于 2010-02-21T19:30:04.473 回答
1

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

于 2010-12-23T18:12:15.650 回答
0

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

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

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

于 2015-05-05T11:56:19.307 回答