与接口的通用协方差 - "之间奇怪的行为矛盾是"和" ="运营商

时间:2016-04-20 00:52:36

标签: c# generics interface casting covariance

在处理协变接口时,我有一个完整的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

HOWEVER ,请考虑以下代码:

AppleBasket appleBasket = new AppleBasket();

Log(appleBasket is IBasket<Fruit>);    

您希望它输出true,对吗?好吧,你错误 - 至少我的编译器是这样的:

false    

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

HOWEVER ,请考虑以下代码:

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

Log(basket is IBasket<Fruit>);

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

当然basket - 声明为IBasket<Fruit> - 必须IBasket<Fruit>的一个实例......我的意思是,那是什么宣称为。正确?

但不是,根据is运算符,你又错了!它输出:

false

...含义IBasket<Fruit> fruit不是IBasket<Fruit>的实例......嗯?

...含义如下:

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

有时可以返回既不是不是IBasket<Fruit>的实例。

进一步,ReSharper告诉我appleBasket is IBasket<Fruit>是多余的,appleBasket 总是提供的类型,并且可以安全地替换为appleBasket != null ...... ReSharper也错了吗?

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

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分: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>