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

Aar*_*ght 74 c# generics

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

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) { }
}
Run Code Online (Sandbox Code Playgroud)

C#编译器拒绝编译它,引用以下规则/错误:

'MyProject.MyFoo <TA>'无法同时实现'MyProject.IFoo <TA>'和'MyProject.IFoo <MyProject.B>',因为它们可能会统一某些类型参数替换

我理解这个错误意味着什么; 如果TA可以是任何东西那么它在技术上也可能B会引入两种不同Handle实现的模糊性.

但TA 不可能是任何东西.基于类型层次结构,TA 不能B- 至少,我不认为它可以. TA必须派生自A,而不是派生出来B,显然在C#/ .NET中没有多类继承.

如果我删除泛型参数和替换TAC,甚至A,它编译.

那么为什么我会收到这个错误?这是编译器中的错误或一般的非智能,还是我还缺少其他东西?

是否有任何解决方法或我只是必须MyFoo为每个可能的TA派生类型重新实现泛型类作为单独的非泛型类?

Eri*_*ert 48

这是C#4规范第13.4.2节的结果,该规范规定:

如果从C创建的任何可能的构造类型将类型参数替换为L后,导致L中的两个接口相同,则C的声明无效.在确定所有可能的构造类型时,不考虑约束声明.

注意那里的第二句话.

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

一般而言,在几乎所有必须推断出关于泛型类型的事实的情况下,忽略约束.约束主要用于确定泛型类型参数的有效基类,而其他几乎没有.

不幸的是,正如您所发现的那样,这有时会导致语言不必要的严格.


一般来说,两次实现"相同"接口的代码气味很差,在某种程度上只能通用泛型类型参数来区分.例如,有一个奇怪的class C : IEnumerable<Turtle>, IEnumerable<Giraffe>东西 - 它既是C又是一系列的乌龟一系列长颈鹿,同时呢?你能描述一下你在这里想要做的事情吗?可能有更好的模式来解决真正的问题.


实际上,您的界面与您描述的完全一致:

interface IFoo<T>
{
    void Handle(T t);
}
Run Code Online (Sandbox Code Playgroud)

然后接口的多重继承提出了另一个问题.你可能会合理地决定使这个界面逆变:

interface IFoo<in T>
{
    void Handle(T t);
}
Run Code Online (Sandbox Code Playgroud)

现在假设你有

interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}
Run Code Online (Sandbox Code Playgroud)

class Danger : IFoo<IABC>, IFoo<IDEF>
{
    void IFoo<IABC>.Handle(IABC x) {}
    void IFoo<IDEF>.Handle(IDEF x) {}
}
Run Code Online (Sandbox Code Playgroud)

而现在事情变得非常疯狂......

IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);
Run Code Online (Sandbox Code Playgroud)

Handle的哪个实现被称为 ???

有关此问题的更多想法,请参阅此文章和评论:

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

  • @Eric:如果你考虑像`IComparable <T>`或`IEquatable <T>`而不是`IEnumerable <T>`这样的接口,那么多次实现相同通用接口的情况就更有可能.拥有一个可以与多种类型进行比较的对象是非常合理的......事实上,对于这个确切的情况,我已经多次遇到类型统一问题. (21认同)
  • @Eric,LBushkin是对的.*某些*使用的是非感性的,并不暗示*所有*使用都是非感性的.这个问题也让我感到困惑,因为我正在实现一个抽象的解析接口IFoo <T0,T1,..>,它实现了一个分段的IParseable <T0>,IParseable <T1>,...带有一组在IParseable上定义的扩展方法,但现在看来不可能.C#/ CLR有很多令人沮丧的角落案例. (3认同)
  • 当然,接口是类型参数是没有意义的; 我错误地认为使用具体类可以解决这个问题.至于场景,该类是一个Saga,*必须*实现几个消息处理程序(每个消息一个是saga的一部分).大约有10几乎相同的传奇,其唯一的区别是确切的具体类型的消息之一,但我不能有一个抽象的消息处理程序,所以我想我会尝试使用一个通用的基类,并只使用一个一堆存根类; 唯一的选择是大量的复制和粘贴. (2认同)

Naw*_*waz 8

显然,它是按照Microsoft Connect中讨论的设计进行的:

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

public interface IIFoo<T> : IFoo<T>
{
}
Run Code Online (Sandbox Code Playgroud)

然后将其实现为:

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

它现在编译精细,单声道.

  • @Aaronaught:类型通过其继承链两次实现相同的接口是合法的(用于隐藏和缓解脆弱的基类).但是同一类型在同一个地方两次实现相同的接口是不合法的 - 因为没有'更好'的接口. (2认同)

Col*_*lin 5

如果将一个接口放在基类上,则可以将其隐藏起来。

public interface IFoo<T> 
{
}

public class Foo<T> : IFoo<T>
{
}

public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
{
}
Run Code Online (Sandbox Code Playgroud)

我怀疑这是有效的,因为如果类型确实“统一”,那么很明显派生类的实现获胜。