尝试添加两个具有不同签名的方法(一个是协变的(时,我看到了非常奇怪的行为。当我尝试添加第二种方法时,它会引发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>>();
}
这样,您可以毫无问题地添加/删除多个基本类型的 T 的委托。缺点当然是你必须对实现接口的每个类都这样做,但它保证了无论 T 如何,只要你使用这些类的事件,它都会工作并且不会引发异常。