泛型类型参数的协方差和多接口实现 [英] Generic type parameter covariance and multiple interface implementations

查看:96
本文介绍了泛型类型参数的协方差和多接口实现的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我有一个协变类型参数的通用接口,这样的:

If I have a generic interface with a covariant type parameter, like this:

interface IGeneric<out T>
{
    string GetName();
}



如果我定义这个类层次:

And If I define this class hierarchy:

class Base {}
class Derived1 : Base{}
class Derived2 : Base{}

然后我可以实现该接口两次单个类,这样,使用显式接口实现:

Then I can implement the interface twice on a single class, like this, using explicit interface implementation:

class DoubleDown: IGeneric<Derived1>, IGeneric<Derived2>
{
   string IGeneric<Derived1>.GetName()
   {
     return "Derived1";
   }

   string IGeneric<Derived2>.GetName()
   {
     return "Derived2";
   }  
}

如果我使用(非通用) DoubleDown 类,并把它转换为 IGeneric< D​​erived1> IGeneric< D​​erived2的> 它的功能预期:

If I use the (non-generic)DoubleDown class and cast it to IGeneric<Derived1> or IGeneric<Derived2> it functions as expected:

var x = new DoubleDown();
IGeneric<Derived1> id1 = x;        //cast to IGeneric<Derived1>
Console.WriteLine(id1.GetName());  //Derived1
IGeneric<Derived2> id2 = x;        //cast to IGeneric<Derived2>
Console.WriteLine(id2.GetName());  //Derived2



然而,铸造 X IGeneric<基地> ,给出了以下结果:

However, casting the x to IGeneric<Base>, gives the following result:

IGeneric<Base> b = x;
Console.WriteLine(b.GetName());   //Derived1



我预计编译器发出一个错误,因为调用的是两个实现之间的暧昧,但它返回的第一个声明的接口。

I expected the compiler to issue an error, as the call is ambiguous between the two implementations, but it returned the first declared interface.

这是为什么不允许?

(通过的实施两个不同的IObservables类?。我想展现给同事,这将失败,但不知何故,它没有)

(inspired by A class implementing two different IObservables?. I tried to show to a colleague that this will fail, but somehow, it didn't)

推荐答案

编译器就行了不能抛出一个错误

The compiler can't throw an error on the line

IGeneric<Base> b = x;
Console.WriteLine(b.GetName());   //Derived1

由于没有歧义,编译器可以知道。 的GetName()其实在接口的有效方法 IGeneric<基地以及GT; 。编译器不跟踪的运行时的时间B 来知道有在那里一个类型,它可能会引起歧义。因此它留给运行时决定做什么。运行时可能会抛出异常,但CLR的设计者显然决定针对(我个人觉得是一个很好的决定)。

because there is no ambiguity that the compiler can know about. GetName() is in fact a valid method on interface IGeneric<Base>. The compiler doesn't track the runtime time of b to know that there is a type in there which could cause an ambiguity. So it's left up to the runtime to decide what to do. The runtime could throw an exception, but the designers of the CLR apparently decided against that (which I personally think was a good decision).

要换个方式,让我们说,而是你根本写的方法:

To put it another way, let's say that instead you simply had written the method:

public void CallIt(IGeneric<Base> b)
{
    string name = b.GetName();
}

和你没有提供的类实施 IGeneric< T> 在汇编。您分发本和许多其他只有一次实现了这个接口,并能够调用你的方法就好了。然而,有人最终将消耗你的程序集并创建 DoubleDown 类,并将其传递到你的方法。在什么时候应该编译器抛出一个错误?当然包含在调用已编发组装的GetName()不能产生一个编译器错误。可以说,从 DoubleDown IGeneric<分配;基地> 产生歧义。但再次,我们可以增加一个间接的另一个层面到原装配:

and you provide no classes implementing IGeneric<T> in your assembly. You distribute this and many others implement this interface only once and are able to call your method just fine. However, someone eventually consumes your assembly and creates the DoubleDown class and passes it into your method. At what point should the compiler throw an error? Surely the already compiled and distributed assembly containing the call to GetName() can't produce a compiler error. You could say that the assignment from DoubleDown to IGeneric<Base> produces the ambiguity. but once again we could add another level of indirection into the original assembly:

public void CallItOnDerived1(IGeneric<Derived1> b)
{
    return CallIt(b); //b will be cast to IGeneric<Base>
}



再次,很多消费者可以打电话或者 CALLIT CallItOnDerived1 键,就好了。但是,我们的消费者通过 DoubleDown 也作出完全合法的呼叫,可能不会造成当他们打电话 CallItOnDerived1 作为一个编译器错误从 DoubleDown 转换为 IGeneric< D​​erived1> 当然应该确定。因此,有没有点,编译器可以对 DoubleDown 的定义抛出其他错误不是可能,但是这将消除这样做可能有用的东西有没有变通的可能

Once again, many consumers could call either CallIt or CallItOnDerived1 and be just fine. But our consumer passing DoubleDown also is making a perfectly legal call that could not cause a compiler error when they call CallItOnDerived1 as converting from DoubleDown to IGeneric<Derived1> should certainly be OK. Thus, there is no point at which the compiler can throw an error other than possibly on the definition of DoubleDown, but this would eliminate the possibility of doing something potentially useful with no workaround.

其实我已经在深入回答了这个问题更多的在其他地方,并且还提供了一个潜在的解决方案,如果该语言可以改变的:

I have actually answered this question more in depth elsewhere, and also provided a potential solution if the language could be changed:

没有警告或错误(或运行时故障)时逆变导致歧义

鉴于语言的机会改变为支持这几乎是零,我认为当前的行为是正常的,除了它必须在规格进行布局,使得在CLR的所有实现将预期行为相同的方式

Given that the chance of the language changing to support this is virtually zero, I think that the current behavior is alright, except that it should be laid out in the specifications so that all implementations of the CLR would be expected to behave the same way.

这篇关于泛型类型参数的协方差和多接口实现的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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