Ответ 1
Одна из причин:
class Foo<out T>
{
T _store;
public T Get()
{
_store = default(T);
return _store;
}
}
Этот класс содержит функцию, которая не является ковариантной, поскольку она имеет поле, а поля могут быть установлены в значения. Это хотя и используется ковариантным способом, потому что ему присваивается значение по умолчанию только когда оно будет только null
для любого случая, когда фактически используется ковариация.
Таким образом, неясно, допустим ли мы это. Не позволяя ему раздражать пользователей (это все равно соответствует тем же потенциальным правилам, которые вы предлагаете), но позволяет это сложно (анализ уже немного запутан, и мы даже не начинаем охотиться за действительно сложными делами).
С другой стороны, анализ этого гораздо проще:
void Main()
{
IFoo<object> foo = new Foo<string>();
Console.WriteLine(foo.Get());
}
interface IFoo<out T>
{
T Get();
}
class Foo<T> : IFoo<T>
{
T _store;
public T Get()
{
_store = default(T);
return _store;
}
}
Легко определить, что ни одна из реализаций IFoo<T>
не разрушает ковариацию, потому что у нее ее нет. Все, что необходимо, это убедиться, что в качестве параметра (в том числе метода setter) не используется T
и.
Тот факт, что ограничение потенциала намного сложнее для класса, чем на интерфейсе по аналогичным причинам, также уменьшает степень полезности ковариантных классов. Они, конечно, не были бы бесполезны, но баланс того, насколько полезными они были бы в отношении того, сколько работы было бы, чтобы указать и реализовать правила о том, что им разрешено делать, намного меньше, чем баланс того, насколько полезны ковариантные интерфейсы о том, как много работы было указать и реализовать их.
Конечно, разницы достаточно, чтобы пройти мимо точки "хорошо, если вы собираетесь разрешить X, было бы глупо не допускать Y...".