接口的通用协变 - "is" 和“=”运算符之间奇怪的行为矛盾
Generic covariance with interfaces - Weird behavioural contradiction between "is" and "=" operators
在处理协变接口时,我有一个完整的 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
转换为 long
。 int
不是 long
的一种,但 long 可以隐式地分配 int 的值。
然而,请考虑以下代码:
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 { ... } }
有时 return 某些东西 两者都不为空 ,并且 不是 IBasket<Fruit>
的实例。
FURTHER,ReSharper 告诉我 appleBasket is IBasket<Fruit>
是多余的,因为 appleBasket
是 always类型,并且可以安全地替换为 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>
。
在处理协变接口时,我有一个完整的 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
转换为 long
。 int
不是 long
的一种,但 long 可以隐式地分配 int 的值。
然而,请考虑以下代码:
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 { ... } }
有时 return 某些东西 两者都不为空 ,并且 不是 IBasket<Fruit>
的实例。
FURTHER,ReSharper 告诉我 appleBasket is IBasket<Fruit>
是多余的,因为 appleBasket
是 always类型,并且可以安全地替换为 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>
。