IEnumerable<T>
интерфейс ковариантен, поэтому IEnumerable<string>
есть IEnumerable<object>
.
Но почему IEnumerable<int>
не IEnumerable<object>
, а int
есть object
?
Не совсем дубликат, stackoverflow.com/questions/1793357/…, но в основном это структуры int, а не объекты.
Или, если вам нужна ссылка на спецификацию: github.com/dotnet/csharpstandard/blob/draft-v7/standard/… — важная часть заключается в том, что «и существует неявная ссылка или преобразование идентификатора из Aᵢ в Bᵢ» — нет ссылка или преобразование личности из int
в object
.
Отвечает ли это на ваш вопрос? Типы значений (целое, десятичное, логическое и т. д.) наследуются от объекта?
Если это поможет, IEnumerable<T> реализует IEnumerable
. Так что, если вы ищете общий интерфейс, и IEnumerable<int>
, и IEnumerable<string>
являются IEnumerable
.
Теперь, когда документы связаны @JonSkeet, должны ли мы преобразовать этот вопрос «почему C#/.Net решил ограничить дисперсию ссылочными типами» или написать вики-ответ со 100% текстом из документов (что не совсем подходит для SO)?
Подобные преобразования дисперсии поддерживаются только в том случае, если существует неявная ссылка или преобразование идентификатора между параметрами типа. Как говорится в спецификации :
Тип
T<Aᵢ, ..., Aᵥ>
преобразуется с помощью дисперсии в типT<Bᵢ, ..., Bᵥ>
, еслиT
является либо интерфейсом, либо типом делегата, объявленным с параметрами вариантного типаT<Xᵢ, ..., Xᵥ>
, и для каждого параметра вариантного типаXᵢ
выполняется одно из следующих условий:
Xᵢ
является ковариантным, и существует неявная ссылка или преобразование идентификатора изAᵢ
вBᵢ
Xᵢ
является контравариантным, и существует неявная ссылка или преобразование идентификатора изBᵢ
вAᵢ
Xᵢ
является инвариантным, и существует преобразование идентичности изAᵢ
вBᵢ
Между int
и object
нет ни ссылки, ни преобразования идентичности, только преобразование бокса.
Вам может быть интересно, что такого особенного в неявных преобразованиях ссылок и идентификаторов, что они допускают преобразования отклонений. Ну, эти преобразования вообще не требуют, чтобы среда выполнения что-либо делала!
Преобразование из string
в object
, например, является исключительно вопросом времени компиляции. Ничего не нужно делать с битами, представляющими string
, чтобы превратить его в object
. В конце концов, каждый string
есть object
.
Из-за этого преобразование из IEnumerable<string>
в IEnumerable<object>
тоже тривиально — среде выполнения не нужно ничего делать.
Однако это неверно для преобразования int
-to-object
. Потому что int
— это тип значения, а object
— ссылочный тип. чтобы поддерживать эту семантику, среда выполнения должна создать оболочку ссылочного типа вокруг этого int
, чтобы выполнить преобразование, «упаковав» его.
Это означает, что среда выполнения не может просто «ничего не делать», если она хочет преобразовать IEnumerable<int>
в IEnumerable<object>
. Вы ожидаете получить из этого IEnumerable
ссылочные типы, а не типы значений, поэтому, когда и как происходит «упаковка»? Когда и как это должно происходить вообще, а не только в случае IEnumerable<T>
?
Я уверен, что в теории все это можно понять, спроектировать и реализовать, но это определенно не будет так тривиально, как в случае с неявными преобразованиями ссылок. На самом деле, я считаю, что это было бы намного сложнее, и потребуются очень радикальные изменения как в языке, так и в среде выполнения, и, вероятно, поэтому это не реализовано.
Из learn.microsoft.com/en-us/dotnet/csharp/programming-guide/… : «Вариантность в универсальных интерфейсах поддерживается только для ссылочных типов. Типы значений не поддерживают дисперсию. Например,
IEnumerable<int>
не может быть неявно преобразуется вIEnumerable<object>
, потому что целые числа представлены типом значения».