首先,请记住 .NETString
既是.NETIConvertible
又是ICloneable
.
现在,考虑以下非常简单的代码:
//contravariance "in"
interface ICanEat<in T> where T : class
{
void Eat(T food);
}
class HungryWolf : ICanEat<ICloneable>, ICanEat<IConvertible>
{
public void Eat(IConvertible convertibleFood)
{
Console.WriteLine("This wolf ate your CONVERTIBLE object!");
}
public void Eat(ICloneable cloneableFood)
{
Console.WriteLine("This wolf ate your CLONEABLE object!");
}
}
然后尝试以下(在某些方法中):
ICanEat<string> wolf = new HungryWolf();
wolf.Eat("sheep");
当一个人编译这个时,一个人不会得到编译器错误或警告。运行它时,看起来调用的方法取决于我的class
声明中接口列表的顺序HungryWolf
。(尝试交换逗号 ( ,
) 分隔列表中的两个接口。)
问题很简单:这不应该给出编译时警告(或在运行时抛出)吗?
我可能不是第一个提出这样的代码的人。我使用了接口的逆变,但是你可以用接口的协变来做一个完全类似的例子。事实上,利珀特先生很久以前就是这样做的。在他博客的评论中,几乎所有人都同意这应该是一个错误。然而,他们默默地允许了这一点。为什么?
---
扩展问题:
上面我们利用aString
既是Iconvertible
(接口)又是ICloneable
(接口)。这两个接口都不是从另一个派生的。
现在这是一个基类的例子,从某种意义上说,它有点糟糕。
请记住,aStackOverflowException
既是SystemException
(直接基类)又是Exception
(基类的基类)。然后(如果ICanEat<>
像以前一样):
class Wolf2 : ICanEat<Exception>, ICanEat<SystemException> // also try reversing the interface order here
{
public void Eat(SystemException systemExceptionFood)
{
Console.WriteLine("This wolf ate your SYSTEM EXCEPTION object!");
}
public void Eat(Exception exceptionFood)
{
Console.WriteLine("This wolf ate your EXCEPTION object!");
}
}
测试它:
static void Main()
{
var w2 = new Wolf2();
w2.Eat(new StackOverflowException()); // OK, one overload is more "specific" than the other
ICanEat<StackOverflowException> w2Soe = w2; // Contravariance
w2Soe.Eat(new StackOverflowException()); // Depends on interface order in Wolf2
}
仍然没有警告、错误或异常。仍然取决于class
声明中的接口列表顺序。但我认为它更糟糕的原因是,这一次有人可能会认为重载决议总是会选择SystemException
,因为它不仅仅是Exception
.
赏金开启前的状态:两位用户的三个回答。
赏金最后一天的状态:仍然没有收到新的答案。如果没有答案出现,我将不得不将赏金奖励给穆斯林 Ben Dhaou。