首先要记住,.NET中的String
既实现了IConvertible
接口,也实现了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");
当编译此代码时,不会出现编译错误或警告。运行时,它看起来像是调用的方法取决于我在
HungryWolf
的class
声明中接口列表的顺序。(尝试交换逗号(,
)分隔列表中的两个接口。)问题很简单:这不应该给出编译时警告(或在运行时抛出异常)吗? 我可能不是第一个想到编写此类代码的人。我使用了接口的逆变性,但您可以使用接口的协变性创建完全类似的示例。事实上,Lippert先生很久以前就做到了这一点。在他的博客评论中,几乎每个人都认为这应该是一个错误。然而,他们默默地允许这样做。为什么? --- 扩展问题: 以上我们利用了
String
既是Iconvertible
(接口)又是ICloneable
(接口)的事实。这两个接口都不是从另一个接口派生的。现在这里有一个基类的例子,在某种意义上,更糟糕一些。
请记住,
StackOverflowException
既是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
}
仍然没有警告、错误或异常。仍然依赖于类声明中接口列表的顺序。但我认为这次更糟糕的原因是,有人可能会认为重载解析总是会选择SystemException
,因为它比Exception
更具体。
奖励开放之前的状态:两个用户提供了三个答案。
奖励截止日当天的状态:仍未收到新答案。如果没有答案出现,我将不得不把奖励授予Moslem Ben Dhaou。
wolf
既不是ICloneable
也不是IConvertible
。它以两种方式实现了ICanEat<>
接口。)请自行尝试。拿走我的HungryWolf
代码。将两个公共方法更改为显式接口实现。运行包含Eat("sheep")
的相同两行代码。你如何知道你的两个显式接口实现中的哪一个被运行?当你尝试后,请再次发表评论。 - Jeppe Stig Nielsen