为什么C#编译器抱怨“类型可能会统一”当它们来自不同的基类时? [英] Why does the C# compiler complain that "types may unify" when they derive from different base classes?
问题描述
我目前的非编译代码与此类似:
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,
其中TA:A
{
public void Handle(TA a){}
public void Handle(B b){}
}
C#编译器拒绝编译,引用以下规则/错误:
'MyProject.MyFoo TA'不能实现'MyProject.IFoo TA'和'MyProject.IFoo
',因为它们可以统一一些类型参数替换
我明白这个错误的含义;如果 TA
可以是任何东西,那么技术上也可以是 B
,这会在两个不同的 Handle
实现。
但TA 不能。至少,根据类型层次结构, TA
不能是 B
我不认为它可以。 TA 必须来自
A
, not 来自 B
,显然C#/ .NET中没有多重类继承。
如果我删除泛型参数并将 TA
与 C
,甚至 A
,它会被编译。
那么为什么我会得到这个错误呢?它是编译器中的一个错误还是一般的非智能,或者是否还有其他我缺少的东西?
有没有解决方法,或者我只是想要为每个可能的 TA
派生类型重新实现 MyFoo
泛型类作为单独的非泛型类?
这是C#4规范第13.4.2节的结果,
如果从C创建的任何可能的构造类型将类型参数替换为L后,导致L中的两个接口相同,则C的声明无效。确定所有可能的构造类型时,不考虑约束声明。
请注意第二句。
因此,它不是编译器中的错误;编译器是正确的。有人可能会认为这是语言规范中的一个缺陷。一般来说,约束在几乎所有情况下都被忽略,在这种情况下必须推导一个关于泛型的事实。约束主要用于确定泛型类型参数的有效基类,而其他的则很少。
不幸的是,这有时会导致语言不必要的严格,正如您发现的那样。 b
$ b
通常,两次实现相同接口的代码味道很差,在某种程度上只能通过泛型类型参数来区分。举例来说,如果有 class C:IEnumerable< Turtle> ;, IEnumerable< Giraffe>
,那么奇怪的是 - 它既是一系列海龟,又是C >和一系列长颈鹿,同时?你能描述一下你在这里想要做的事吗?可能有更好的模式来解决真正的问题。
如果实际上您的界面与您描述的完全相同:
界面IFoo< T>
{
void Handle(T t);
}
接口的多重继承存在另一个问题。您可能会合理地决定使此接口逆变:
interface IFoo< in T>
{
void Handle(T t);
$ / code>
现在假设您有
接口IABC {} $ b $接口IDEF {} $ b $接口IABCDEF:IABC,IDEF {}
和
class危险:IFoo< IABC> ;, IFoo< IDEF>
{
void IFoo< IABC> .Handle(IABC x){}
void IFoo< IDEF> .Handle(IDEF x){}
}
现在事情变得非常疯狂......
的IFoo< IABCDEF> crazy = new Danger();
crazy.Handle(null);
有关此问题的更多信息,请参阅本文和评论:
My current non-compiling code is similar to this:
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) { }
}
The C# compiler refuses to compile this, citing the following rule/error:
'MyProject.MyFoo<TA>' cannot implement both 'MyProject.IFoo<TA>' and 'MyProject.IFoo<MyProject.B>' because they may unify for some type parameter substitutions
I understand what this error means; if TA
could be anything at all then it could technically also be a B
which would introduce ambiguity over the two different Handle
implementations.
But TA can't be anything. Based on the type hierarchy, TA
can't be a B
- at least, I don't think it can. TA
must derive from A
, which does not derive from B
, and obviously there's no multiple class inheritance in C#/.NET.
If I remove the generic parameter and replace TA
with C
, or even A
, it compiles.
So why do I get this error? Is it a bug in or general un-intelligence of the compiler, or is there something else I'm missing?
Is there any workaround or am I just going to have to re-implement the MyFoo
generic class as a separate non-generic class for every single possible TA
derived type?
This is a consequence of section 13.4.2 of the C# 4 specification, which states:
If any possible constructed type created from C would, after type arguments are substituted into L, cause two interfaces in L to be identical, then the declaration of C is invalid. Constraint declarations are not considered when determining all possible constructed types.
Note that second sentence there.
It is therefore not a bug in the compiler; the compiler is correct. One might argue that it is a flaw in the language specification.
Generally speaking, constraints are ignored in almost every situation in which a fact must be deduced about a generic type. Constraints are mostly used to determine the effective base class of a generic type parameter, and little else.
Unfortunately, that sometimes leads to situations where the language is unnecessarily strict, as you have discovered.
It is in general a bad code smell to implement "the same" interface twice, in some way distinguished only by generic type arguments. It is bizarre, for example, to have class C : IEnumerable<Turtle>, IEnumerable<Giraffe>
-- what is C that it is both a sequence of turtles, and a sequence of giraffes, at the same time? Can you describe the actual thing you're trying to do here? There might be a better pattern to solve the real problem.
If in fact your interface is exactly as you describe:
interface IFoo<T>
{
void Handle(T t);
}
Then multiple inheritance of the interface presents another problem. You might reasonably decide to make this interface contravariant:
interface IFoo<in T>
{
void Handle(T t);
}
Now suppose you have
interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}
And
class Danger : IFoo<IABC>, IFoo<IDEF>
{
void IFoo<IABC>.Handle(IABC x) {}
void IFoo<IDEF>.Handle(IDEF x) {}
}
And now things get really crazy...
IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);
Which implementation of Handle gets called???
See this article and the comments for more thoughts on this issue:
这篇关于为什么C#编译器抱怨“类型可能会统一”当它们来自不同的基类时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!