Почему `IEnumerable<int>` не `IEnumerable<object>`?

IEnumerable<T> интерфейс ковариантен, поэтому IEnumerable<string> есть IEnumerable<object>. Но почему IEnumerable<int> не IEnumerable<object>, а int есть object?

Из learn.microsoft.com/en-us/dotnet/csharp/programming-guide/… : «Вариантность в универсальных интерфейсах поддерживается только для ссылочных типов. Типы значений не поддерживают дисперсию. Например, IEnumerable<int> не может быть неявно преобразуется в IEnumerable<object>, потому что целые числа представлены типом значения».

Jon Skeet 28.11.2022 21:28

Не совсем дубликат, stackoverflow.com/questions/1793357/…, но в основном это структуры int, а не объекты.

gilliduck 28.11.2022 21:29

Или, если вам нужна ссылка на спецификацию: github.com/dotnet/csharpstandard/blob/draft-v7/standard/… — важная часть заключается в том, что «и существует неявная ссылка или преобразование идентификатора из Aᵢ в Bᵢ» — нет ссылка или преобразование личности из int в object.

Jon Skeet 28.11.2022 21:29

Если это поможет, IEnumerable<T> реализует IEnumerable. Так что, если вы ищете общий интерфейс, и IEnumerable<int>, и IEnumerable<string> являются IEnumerable.

Gabriel Luci 28.11.2022 21:33

Теперь, когда документы связаны @JonSkeet, должны ли мы преобразовать этот вопрос «почему C#/.Net решил ограничить дисперсию ссылочными типами» или написать вики-ответ со 100% текстом из документов (что не совсем подходит для SO)?

Alexei Levenkov 28.11.2022 21:35
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
6
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Подобные преобразования дисперсии поддерживаются только в том случае, если существует неявная ссылка или преобразование идентификатора между параметрами типа. Как говорится в спецификации :

Тип 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>?

Я уверен, что в теории все это можно понять, спроектировать и реализовать, но это определенно не будет так тривиально, как в случае с неявными преобразованиями ссылок. На самом деле, я считаю, что это было бы намного сложнее, и потребуются очень радикальные изменения как в языке, так и в среде выполнения, и, вероятно, поэтому это не реализовано.

Другие вопросы по теме