为什么c#编译器抱怨“类型可能统一”?当它们从不同的基类派生时
本文关键字:派生 基类 编译器 类型 为什么 | 更新日期: 2023-09-27 18:10:26
我当前的非编译代码类似于:
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
'不能同时实现'MyProject.IFoo '和'MyProject.IFoo ',因为它们可能会统一一些类型参数替换
我明白这个错误是什么意思;如果TA
可以是任何东西,那么它在技术上也可以是B
,这将在两种不同的Handle
实现中引入歧义。
但是TA 不可能是任何东西。基于类型层次结构,TA
不能是B
-至少,我不认为可以。TA
必须从A
派生,而不能从B
派生,显然c#/. net中没有多类继承。
如果我删除泛型参数并将TA
替换为C
,甚至A
,它将编译。
为什么会出现这个错误?它是编译器中的错误或一般的不智能,还是我遗漏了其他东西?
是否有任何解决方法,或者我只是要重新实现MyFoo
泛型类作为一个单独的非泛型类为每一个可能的TA
派生类型?
这是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
显然这是在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编译得很好。
如果你把一个接口放在一个基类上,你可以偷偷地把它隐藏起来。
public interface IFoo<T>
{
}
public class Foo<T> : IFoo<T>
{
}
public class Foo<T1, T2> : Foo<T1>, IFoo<T2>
{
}
我怀疑这是有效的,因为如果类型"统一";很明显,派生类的实现获胜。
看我对基本相同问题的回答:https://stackoverflow.com/a/12361409/471129
在某种程度上,这是可以做到的!我使用了一种区分方法,而不是限定符来限制类型。
它没有统一,事实上,它可能比统一更好,因为你可以把单独的接口分开。
在这里看到我的帖子,在另一个上下文中有一个完整的工作示例。https://stackoverflow.com/a/12361409/471129
基本上,你要做的就是给IIndexer添加另一个类型参数,这样它就变成了IIndexer <TKey, TValue, TDifferentiator>
。
那么当你使用它两次时,你将"First"传递给第一次使用,"Second"传递给第二次使用
所以,类Test变成:class Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>
因此,您可以执行new Test<int,int>()
其中First和Second是平凡的:
interface First { }
interface Second { }
现在猜一猜…
A、B和C不能在外部程序集中声明吗?在外部程序集中,MyFoo
简单的解决方法就是实现Handle(A)而不是Handle(TA)(并使用IFoo而不是IFoo
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) { }
}
我知道自从这个帖子发布以来已经有一段时间了,但是对于那些通过搜索引擎寻求帮助的人来说。请注意,'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 }
}
}