为什么C#编译器抱怨“类型可能会统一”当它们来自不同的基类时? [英] Why does the C# compiler complain that "types may unify" when they derive from different base classes?

查看:126
本文介绍了为什么C#编译器抱怨“类型可能会统一”当它们来自不同的基类时?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  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);

???



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

http://blogs.msdn .com / b / ericlippert / archive / 2007/11/09 /协变与协变-c-part-10-dealing-with-ambiguity.aspx

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:

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

这篇关于为什么C#编译器抱怨“类型可能会统一”当它们来自不同的基类时?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆