Подтвердить что ты не робот

Почему общие интерфейсы по умолчанию не совпадают/контравариантны?

Например, интерфейс IEnumerable<T>:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

В этом интерфейсе общий тип используется только как возвращаемый тип метода интерфейса и не используется в качестве типа аргументов метода, поэтому он может быть ковариантным. Давая это, теоретически не может компилятор вывести дисперсию из интерфейса? Если это возможно, то почему С# требует, чтобы мы задавали ключевые слова co/contravariance явно.

Обновление. Как сказал Джон Скит, этот вопрос можно разделить на следующие вопросы:

  • Может компилятор выводит общий тип co/contravariance на то, как он используется внутри текущего родового типа и всех его базовых типов?

    Например. Сколько общих параметров интерфейса из .NET Framework 4.0 может быть отмечено co/contravariant автоматически без какой-либо двусмысленности? Около 70%, 80%, 90% или 100%?

  • Если это возможно, должен применять по умолчанию co/contravariance к родовым типам? По крайней мере, для тех типов, которые он способен анализировать и вывести co/contravariance из использования типа.

4b9b3361

Ответ 1

Ну, здесь есть два вопроса. Во-первых, может ли компилятор всегда это делать? Во-вторых, должен ли он (если он может)?

По первому вопросу, я отдам Эрику Липперту, который сделал этот комментарий, когда я привел именно эту проблему во 2-е издание С# в глубину:

Мне не ясно, что мы разумно могли бы даже, если бы захотели. Мы можем легко подойти с ситуациями, требующими дорогого глобального анализа всех интерфейсов в программе выработать отклонения, и мы можем легко найти ситуации, когда <in T, out U> или <out T, in U> и не может решить между ними. И с плохими производительность и неоднозначные случаи маловероятны.

(Надеюсь, Эрик не возражает против того, чтобы я цитировал этот дословный текст, он ранее очень хорошо делился такими прозрениями, поэтому я иду мимо прошлой формы:)

С другой стороны, я подозреваю, что все еще есть случаи, когда их можно вывести без какой-либо двусмысленности, поэтому вторая точка по-прежнему актуальна...

Я не думаю, что это должно быть автоматическим, даже если компилятор может однозначно знать, что он действителен только одним способом. Хотя расширение интерфейса всегда в какой-то степени является нарушением, обычно это не так, если вы только его реализуете. Однако, если люди полагаются на ваш интерфейс, чтобы быть вариантом, вы не сможете добавлять к нему методы, не разбивая клиентов... даже если они просто звонящие, а не исполнители. Добавленные вами методы могут изменить ранее ковариантный интерфейс, чтобы стать инвариантным, и в этот момент вы разбиваете всех вызывающих, которые пытаются использовать его ковариантно.

В принципе, я считаю, что это нужно, чтобы это было явным - это дизайнерское решение, которое вы должны делать сознательно, а не просто случайно констатировать ковариацию/контравариантность, не подумав об этом.

Ответ 2

В этой статье объясняется, что есть ситуации, когда компилятор не может вывести и поэтому предоставляет вам явный синтаксис:

interface IReadWriteBase<T>
{
    IReadWrite<T> ReadWrite();
}

interface IReadWrite<T> : IReadWriteBase<T>
{

}

Что вы здесь делаете in или out, оба работают?