ArgumentException:向事件添加协变方法时不兼容的委托类型

ArgumentException: Incompatible Delegate Types when adding covariant methods to event

我在尝试添加两个具有不同签名(彼此协变)的方法时看到了非常奇怪的行为。当我尝试添加第二种方法时,它会抛出 ArgumentException: Incompatible Delegate Types

public class SomeClass { } // Just a class that inherits from object

public interface GenericInterface<out T> { // An interface with a covariant parameter T
    event System.Action<T> doSomethingWithT;
}

public interface SpecificInterface : GenericInterface<SomeClass> { } // A more specific interface where T = SomeClass

public class ImpClass: SpecificInterface {  // An implementation of the more specific interface
    public event System.Action<SomeClass> doSomethingWithT;
}

基本上是一个简单的泛型接口,其中泛型参数是协变的,一个将类型分配给泛型的子接口,以及一个子接口的实现。

这是抛出异常的代码:

protected void Start() {
    ImpClass impObj = new ImpClass();
    GenericInterface<object> genericObj = impObj; // assignment possible because interface is covariant

    impObj.doSomethingWithT += DoSomethingSpecific; 
    genericObj.doSomethingWithT += DoSomething; // this line throws an exception
}

protected void DoSomething(object o) { }
protected void DoSomethingSpecific(SomeClass o) { }

现在代码可以正常编译,只添加更具体或更通用的方法,每个方法都可以正常工作,但如果我尝试同时添加两者,则会出现异常。

没有意义。知道为什么吗?有什么解决办法吗?

至于一个可能的解决方案,您可以使用特定类型的引用来添加两个处理程序,并且由于协方差它工作正常:

impObj.doSomethingWithT += DoSomethingSpecific; 
impObj.doSomethingWithT += DoSomething; 

至于原因,我只能提供一个有根据的猜测:运行时不允许将具有不同类型参数的处理程序附加到具有泛型类型的委托,即使协变规则有效编译器有关。并且泛型类型 (System.Action<T>) 的委托正是您在使用 genericObj 引用时访问的对象,即使在创建 impObj 时它已经使用具体参数类型进行了初始化.

我仍然没有找到关于为什么会发生这种情况的解释,但我确实找到了可以让您执行此操作的解决方法。您必须实现事件的访问器并将委托保存在单独的列表或哈希集中,而不是使用内置事件实现。

public class ImpClass: SpecificInterface {  // An implementation of the more specific interface
    public event System.Action<SomeClass> doSomethingWithT { 
        add { delegateSubs.Add(value); } 
        remove { delegateSubs.Remove(value); } 
    }

    protected HashSet<System.Action<SomeClass>> delegateSubs = new HashSet<System.Action<SomeClass>>();
}

这样你就可以 add/remove 代表 T 的多个基本类型而没有任何问题。缺点当然是你必须为每个实现接口的 class 这样做,但它保证无论何时你使用这些 classes 的事件,无论 T,它都会工作而不是抛出异常。