具有接口的泛型协方差 - "is" 运算符和"="运算符之间的奇怪行为矛盾



在处理协变接口时,我有一个完整的wtf时刻。

考虑以下内容:

class Fruit { }
class Apple : Fruit { }
interface IBasket<out T> { }
class FruitBasket : IBasket<Fruit> { }
class AppleBasket : IBasket<Apple> { }

注:

  • AppleBasket不继承自FruitBasket
  • IBasket协变量

稍后在脚本中,您会写道:

FruitBasket fruitBasket = new FruitBasket();
AppleBasket appleBasket = new AppleBasket();
Log(fruitBasket is IBasket<Fruit>);
Log(appleBasket is IBasket<Apple>);

正如你所期望的,输出是:

true
true

但是,请考虑以下代码:

AppleBasket appleBasket = new AppleBasket();
Log(appleBasket is IBasket<Fruit>);    

你会期望它输出true,对吧?好吧,你错了——至少我的编译器是这样的:

false    

这很奇怪,但它可能正在执行隐式转换,类似于将int转换为longint不是long的一种,但long可以隐式地指定int的值。

但是,请考虑以下代码:

IBasket<Fruit> basket = new AppleBasket(); // implicit conversion?
Log(basket is IBasket<Fruit>);

这段代码运行得很好——没有编译器错误或异常——尽管我们之前了解到AppleBasket不是IBasket<Fruit>的一种。除非有第三个选项,否则它必须在赋值中进行隐式转换。

当然,声明为IBasket<Fruit>-basket-必须IBasket<Fruit>的实例。。。我的意思是,这就是声明的内容。对吧?

但不,根据is运算符的说法,你又错了输出:

false

意味着IBasket<Fruit> fruit不是IBasket<Fruit>的实例。。。嗯?

表示以下属性:

IBasket<Fruit> FruitBasket { get { ... } }

有时可以返回两者都不是null的东西,并且不是IBasket<Fruit>的实例


此外,ReSharper告诉我appleBasket is IBasket<Fruit>是多余的,因为appleBasket总是所提供的类型,并且可以安全地用appleBasket != null替换。。。ReSharper也搞错了?

那么,这里发生了什么?这只是我的C#版本(Unity 5.3.1p4-它是Unity自己的Mono分支,基于.NET 2.0)吗?

根据您的评论,协方差没有得到适当的支持也就不足为奇了。。。它被添加到C#4中。

IS令人难以置信的是,如果它的目标是基于.NET 2.0 的端口,它甚至会进行编译

您之所以能够声明这一点,有一个原因:

IBasket<Fruit> basket = new AppleBasket();

因为此接口没有引用<T>的方法。

interface IBasket<out T> { }

编译器没有什么可以保护您的,因为您只能声明类型。你不能对他们做任何事。尝试向接口添加一个方法:

interface IBasket<out T>
{ 
    void Add(T item);
}

这就是编译器将引发错误的地方,要求您从接口中删除协方差(out)。如果你删除它,那么它现在不会编译:

IBasket<Fruit> basket = new AppleBasket()

因为这样就可以将非Apple对象添加到IBasket<Apple>中。

最新更新