Во-первых, помните, что .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");
Когда кто-то компилирует это, вы не получаете никакой ошибки компилятора или предупреждения. При запуске он выглядит так, как вызванный метод зависит от порядка списка интерфейсов в моем объявлении class
для HungryWolf
. (Попробуйте поменять местами два интерфейса в разделенном запятой (,
).)
Вопрос прост: Должно ли это давать предупреждение во время компиляции (или бросать во время выполнения)?
Я, вероятно, не первый, кто придумал такой код. Я использовал контравариантность интерфейса, но вы можете сделать совершенно аналогичный пример с covarainace интерфейса. И фактически Мистер Липперт сделал именно это давным-давно. В комментариях в своем блоге почти все согласны с тем, что это должна быть ошибка. Но они позволяют это молча. Почему?
---
Расширенный вопрос:
Выше мы использовали, что 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
}
По-прежнему нет предупреждений, ошибок или исключений. Все еще зависит от порядка списка интерфейсов в объявлении class
. Но причина, по которой мне кажется хуже, заключается в том, что на этот раз кто-то может подумать, что разрешение перегрузки всегда будет выбирать SystemException
, потому что это более специфично, чем просто Exception
.
Статус до открытия щели: три ответа от двух пользователей.
Статус в последний день щедрости: по-прежнему новых ответов не получено. Если ответы не появятся, я должен наградить премию Муслиму Бен Дхау.