C# 为什么C“编译器会抱怨”;类型可以统一;当它们派生自不同的基类时?

C# 为什么C“编译器会抱怨”;类型可以统一;当它们派生自不同的基类时?,c#,generics,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 Handl

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

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) { }
}
公共抽象类A{}
公共类B{}
公共类C:A{}
公共接口IFoo
{
无效手柄(T项);
}
公共类MyFoo:IFoo,IFoo
TA:A在哪里
{
公共无效句柄(TA a){}
公共无效句柄(B){}
}
C#编译器拒绝编译此文件,引用以下规则/错误:

“MyProject.MyFoo”无法同时实现“MyProject.IFoo”和“MyProject.IFoo”,因为它们可能会统一用于某些类型参数替换

我理解这个错误的含义;如果
TA
可以是任何东西,那么从技术上讲它也可以是a
B
,这将在两个不同的
句柄
实现上引入歧义

但是TA不可能是什么。基于类型层次结构,
TA
不可能是a
B
——至少,我认为它不可能
TA
必须从
A
派生,而A不是从
B
派生的,显然C#/.NET中没有多类继承

如果我删除泛型参数并将
TA
替换为
C
,甚至
A
,它将编译

那么为什么我会得到这个错误呢?这是编译器中的一个bug还是一般的非智能性,或者我还缺少什么


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

显然,它是按Microsoft Connect讨论的设计实现的:

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

public interface IIFoo<T> : IFoo<T>
{
}
公共接口IIFoo:IFoo
{
}
然后将其改为:

public class MyFoo<TA> : IIFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}
公共类MyFoo:IIFoo,IFoo TA:A在哪里 { 公共无效句柄(TA a){} 公共无效句柄(B){} } 它现在编译得很好。

现在猜猜看

难道不能在外部程序集中声明A、B和C吗?因为在编译MyFoo之后,类型层次结构可能会发生变化,从而给世界带来灾难

简单的解决方法就是实现Handle(A)而不是Handle(TA)(并使用IFoo而不是IFoo)。无论如何,使用Handle(TA)所做的事情都比不上从A访问方法(由于A:TA约束)

公共类MyFoo:IFoo,IFoo{ 公共无效句柄(A){} 公共无效句柄(B){} } 嗯,这个怎么样:

public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    void IFoo<TA>.Handle(TA a) { }
    void IFoo<B>.Handle(B b) { }
}
公共类MyFoo:IFoo,IFoo TA:A在哪里 { void IFoo.Handle(TA a){} void IFoo.Handle(B){} }
这是C#4规范第13.4.2节的结果,其中规定:

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

注意这里的第二句话

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

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

不幸的是,正如您所发现的,这有时会导致语言过于严格


通常,两次实现“同一”接口是一种不好的代码味道,在某种程度上仅通过泛型类型参数来区分。例如,有
类C:IEnumerable,IEnumerable
——C是什么?它既是一个海龟序列,又是一个长颈鹿序列?你能描述一下你在这里要做的事情吗?也许有更好的模式来解决真正的问题


如果您的界面与您描述的完全相同:

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

类危险:IFoo,IFoo
{
void IFoo.Handle(iabcx){}
void IFoo.Handle(IDEF x){}
}
现在事情变得很疯狂

IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);
IFoo疯狂=新的危险();
疯狂。句柄(null);
句柄的哪个实现被调用

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


请看我对基本相同问题的回答:

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

它没有统一,事实上,它可能比它统一要好,因为您可以将单独的接口分离开来

请参阅我在这里的文章,在另一个上下文中提供了一个完全有效的示例。

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

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

因此,类测试变成:class
Test:IIndexer,IIndexer

因此,您可以执行
newtest()

其中,第一个和第二个是微不足道的:

interface First { }

interface Second { }

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

}
公共类MyFoo:IFoo其中TA:Base其中B:Base
{
公共无效句柄(基本对象)
{ 
如果(obj是TA){//TA特定代码或调用}
else如果(obj是B){//B特定代码或调用}
}
}

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

public interface IFoo<T> 
{
}

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

public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
{
}
公共接口IFoo
{
}
公共类Foo:IFoo
{
}
公共类Foo:Foo,IFoo
{
}
我怀疑这是可行的,因为如果类型“统一”,则派生类的
IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);
interface First { }

interface Second { }
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 }
    }

}
public interface IFoo<T> 
{
}

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

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