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

时间:2011-10-05 16:47:25

标签: 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) { }
}

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

  

'MyProject.MyFoo&LT; TA&GT;'无法实现'MyProject.IFoo&lt; TA&gt;'和'MyProject.IFoo&lt; MyProject.B&gt;'因为他们可能统一某些类型参数替换

我理解这个错误意味着什么;如果TA可以是任何内容,那么它在技术上也可能是B,这会在两个不同的Handle实现中引入歧义。

但TA 不能成为任何东西。根据类型层次结构,TA 不能成为B - 至少,我不会认为TA必须派生自A 派生自B,显然C#/ .NET中没有多个类继承。

如果我删除了通用参数并将TA替换为C,甚至A,则会进行编译。

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

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

7 个答案:

答案 0 :(得分:47)

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

  

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

请注意那里的第二句话。

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

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

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


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


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

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

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

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

答案 1 :(得分:8)

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

解决方法是,将另一个界面定义为:

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) { }
}

现在编译好mono

答案 2 :(得分:2)

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

在某种程度上,这可以做到!我使用差异化方法,而不是限制类型的限定符。

它没有统一,事实上它可能比它更好,因为你可以分开挑逗单独的接口。

在此处查看我的帖子,在另一个上下文中提供完整的工作示例。 https://stackoverflow.com/a/12361409/471129

基本上,您要做的是向 IIndexer 添加另一个类型参数,使其成为IIndexer <TKey, TValue, TDifferentiator>

然后当你使用它两次时,你将“First”传递给第一次使用,将第二次传递给第二次使用

因此,类Test变为:class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

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

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

interface First { }

interface Second { }

答案 3 :(得分:1)

我知道自帖子发布以来已经有一段时间了但是对于那些通过搜索引擎来寻求帮助的人来说。请注意,'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 }
    }

}

答案 4 :(得分:0)

现在猜一下......

无法在外部程序集中声明A,B和C,在编译MyFoo&lt; T&gt;之后类型层次结构可能会发生变化,从而对世界造成严重破坏?

简单的解决方法只是实现句柄(A)而不是句柄(TA)(并使用IFoo&lt; A&gt;而不是IFoo&lt; TA&gt;)。无论如何,你不能使用Handle(TA)比A中的访问方法(由于A:TA约束)做得更多。

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

答案 5 :(得分:0)

嗯,这个怎么样:

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

答案 6 :(得分:0)

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

public interface IFoo<T> {
}

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

public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
{
}