为什么C#编译器在从不同的基类派生时抱怨“types可能会统一”?

我目前的非编译代码与此类似:

public abstract class A { } public class B { } public class C : A { } public interface IFoo<T> { void Handle(T item); } public class MyFoo<TA> : IFoo<TA>, IFoo<B> where TA : A { public void Handle(TA a) { } public void Handle(B b) { } } 

C#编译器拒绝编译这个,引用下面的规则/错误:

'MyProject.MyFoo <TA>'不能实现'MyProject.IFoo <TA>'和'MyProject.IFoo <MyProject.B>',因为它们可能会统一一些types参数replace

我明白这个错误的含义 如果TA可以是任何东西,那么它在技术上也可能是B ,这会在两个不同的Handle实现中引入歧义。

但是TA 不可能是任何东西。 基于types层次结构, TA 不可能B – 至less我不这么认为TA必须从A派生,而B 不是B派生的,显然在C#/。NET中没有多重类inheritance。

如果我删除通用参数,并用CAreplaceTA ,则编译。

那么为什么我会得到这个错误? 它是编译器中的一个错误还是一般的非智能,还是还有别的东西我错过了?

是否有任何解决方法,或者我将不得不重新实现MyFoogenerics类作为一个单独的非generics类为每个可能的TA派生types?

这是C#4规范的第13.4.2节的一个结果,它指出:

如果从C创build的任何可能的构造types将types参数replace为L之后,导致L中的两个接口相同,则C的声明无效。 确定所有可能的构造types时,不考虑约束声明。

注意那里的第二句话。

因此它不是编译器中的一个错误。 编译器是正确的。 有人可能会认为这是语言规范中的一个缺陷。

一般来说,在几乎所有的情况下,约束都被忽略,在这种情况下必须推导一个关于generics的事实。 约束主要用于确定genericstypes参数的有效基类 ,而其他的则很less。

不幸的是,有时会导致语言不必要的严格的情况,就像你发现的那样。


通常两次实现“相同”接口的代码味道很差,在某种程度上只能通过genericstypes参数来区分。 例如class C : IEnumerable<Turtle>, IEnumerable<Giraffe> – C是什么,它既是一串乌龟是一串长颈鹿, 同时呢? 你能描述一下你在这里想要做的事吗? 解决真正的问题可能有更好的模式。


如果实际上你的界面和你描述的一模一样:

 interface IFoo<T> { void Handle(T t); } 

然后,接口的多重inheritance存在另一个问题。 你可能会合理地决定使这个接口是逆向的:

 interface IFoo<in T> { void Handle(T t); } 

现在假设你有

 interface IABC {} interface IDEF {} interface IABCDEF : IABC, IDEF {} 

 class Danger : IFoo<IABC>, IFoo<IDEF> { void IFoo<IABC>.Handle(IABC x) {} void IFoo<IDEF>.Handle(IDEF x) {} } 

现在事情变得非常疯狂

 IFoo<IABCDEF> crazy = new Danger(); crazy.Handle(null); 

Handle的哪个实现被调用

有关此问题的更多信息,请参阅本文和注释。

http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx

显然这是在Microsoft Connect上讨论的devise:

  • 允许在某些条件下为通用类中的多个types参数实现相同的通用接口

解决方法是,定义另一个接口为:

 public interface IIFoo<T> : IFoo<T> { } 

然后将其实现为:

 public class MyFoo<TA> : IIFoo<TA>, IFoo<B> where TA : A { public void Handle(TA a) { } public void Handle(B b) { } } 

它现在编译好, 单声道 。

看到我的回答基本上相同的问题在这里: https : //stackoverflow.com/a/12361409/471129

在某种程度上,这可以做到! 我使用区分方法,而不是限定types的限定符。

它并不统一,实际上它可能比如果这样做更好,因为你可以把单独的接口分开。

在这里看到我的文章,在另一个上下文中有一个完整的工作示例 https://stackoverflow.com/a/12361409/471129

基本上,你所做的就是向IIndexer添加另一个types参数,使它成为IIndexer <TKey, TValue, TDifferentiator>

那么当你使用它两次,你通过第一次使用“第一”,第二次使用“秒”

所以,类Test成为:class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

因此,你可以做new Test<int,int>()

第一,第二是微不足道的:

 interface First { } interface Second { } 

我知道这个post已经发布了一段时间,但是对于那些通过search引擎寻求帮助的人来说, 请注意,“Base”代表TA和B的基类。

 public class MyFoo<TA> : IFoo<Base> where TA : Base where B : Base { public void Handle(Base obj) { if(obj is TA) { // TA specific codes or calls } else if(obj is B) { // B specific codes or calls } } } 

现在猜测…

A,B和C不能在外部程序集中声明,在编译MyFoo <T>之后types层次可能会改变,给世界带来破坏?

简单的解决方法是实现句柄(A)而不是句柄(TA)(并使用IFoo <A>而不是IFoo <TA>)。 无论如何,您不能使用Handle(TA)做比A的访问方法更多的操作(由于A:TA限制)。

 public class MyFoo : IFoo<A>, IFoo<B> { public void Handle(A a) { } public void Handle(B b) { } } 

嗯,这个怎么样:

 public class MyFoo<TA> : IFoo<TA>, IFoo<B> where TA : A { void IFoo<TA>.Handle(TA a) { } void IFoo<B>.Handle(B b) { } }