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

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

问题描述

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

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 类并将其转换为 IGenericIGeneric 它会起作用正如预期的那样:

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 上的有效方法.编译器不会跟踪 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 type 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 的类.您分发这个和许多其他人只实现一次这个接口,并且能够很好地调用您的方法.但是,有人最终会使用您的程序集并创建 DoubleDown 类并将其传递到您的方法中.编译器应该在什么时候抛出错误?当然,包含对 GetName() 调用的已编译和分发的程序集不会产生编译器错误.你可以说从 DoubleDownIGeneric 的赋值产生了歧义.但我们可以再一次在原始程序集中添加另一层间接:

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>
}

再一次,许多消费者可以调用 CallItCallItOnDerived1 并且很好.但是我们的消费者传递 DoubleDown 也是在进行完全合法的调用,当他们调用 CallItOnDerived1DoubleDown 转换为 时不会导致编译器错误code>IGeneric 应该没问题.因此,除了 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天全站免登陆