2016-04-20 11 views
4

当处理协变接口时,我有一个完整的wtf时刻。带接口的泛型协方差 - “is”和“=”运算符之间的奇怪行为矛盾

考虑以下几点:

class Fruit { } 
class Apple : Fruit { } 

interface IBasket<out T> { } 

class FruitBasket : IBasket<Fruit> { } 
class AppleBasket : IBasket<Apple> { } 

注:

  • AppleBasket不从​​继承。
  • IBasketcovariant

后来在脚本,你写的:

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  

这是奇怪的,但也许它的执行隐式转换,类似转换的intlongint不是long的一种,但long可以隐式地赋予int的值。

无论其,考虑下面的代码:

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

Log(basket is IBasket<Fruit>); 

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

当然basket - 声明为IBasket<Fruit> - MUSTIBasket<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)是一个坚果吗?

+3

呃,你运行的是什么版本的.NET? 'appleBasket is IBasket '为我打印'true'。看[小提琴](https://dotnetfiddle.net/aiNzDA)复制。 – Rob

+1

对我来说它工作,即使我有兴趣看看哪个版本的.Net。 –

+0

在Unity 5.3.1p4中提供的Mono版本 - 它是Unity基于.NET 2.0的Mono自己的分支 – Hatchling

回答

1

根据您的意见,协作不恰当地被支持......这是在C#4中添加的。

什么IS令人难以置信的令人惊讶的是,它甚至在编译所有,如果它是基于关闭的.NET 2.0

+0

猜测我现在正在Unity bug报告土地。祝我好运! – Hatchling

+1

这可能是一场漫长而艰苦的战斗。希望它对你有好处:) –

+0

来自Unity QA:“我设法重现了这个问题,幸运的是,它已经解决了,并且不再可以在Unity 5.5.0a4版本中重现,请测试Unity 5.5将会公开的问题如果问题仍然存在,请告诉我。“ – Hatchling

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>

+0

实际上,在IBasket中声明OUTPUT方法对此行为没有影响。 例如:interface IBasket {T Item {get;尽管如此,声明一个将T作为输入的方法(例如您提供的方法)将会生成一个错误。在这种情况下,您需要使用。 – Hatchling