【问题标题】:ArgumentException: Incompatible Delegate Types when adding covariant methods to eventArgumentException:向事件添加协变方法时委托类型不兼容
【发布时间】:2019-12-14 10:00:57
【问题描述】:

我在尝试添加具有不同签名的两种方法时看到了非常奇怪的行为(它们之间是协变的)。当我尝试添加第二种方法时,它会抛出一个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) { }

现在代码编译得很好,并且只添加更具体或更通用的方法,每个方法都可以单独工作,但如果我尝试同时添加两者,我会得到异常。

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

【问题讨论】:

  • 我假设CovClassImpClass? (错字)
  • 请看this answer
  • @JuanR 是的,正在修复

标签: c# covariance


【解决方案1】:

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

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

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

【讨论】:

  • 令人惊讶的是,这行得通。但是,如果我不能使用协变接口,它就有点过分了......所以它不能解决问题。
【解决方案2】:

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

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,它都会工作并且不会抛出异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 2012-02-01
    相关资源
    最近更新 更多