Почему типы значений наследуются от ссылочных типов?
У меня есть два вопроса:
-
Мы знаем, что все типы получены из Object
, который является ссылочным типом. Мой вопрос: почему int
- тип value - наследуется от ссылочного типа Object
? Возможно ли это?
-
Если int
является производным от Object
, зачем нам нужно вводить поле при передаче int
в функцию, которая ожидает Object
в качестве параметра? Обычно со ссылками, когда вам нужно передать объект производного типа в качестве параметра функции, ожидающего объект базового типа, вам не нужно делать ничего дополнительного. Почему ящик здесь?
Для меня эта ситуация кажется проблемой в том, как была разработана эта иерархия типов.
PS. Я нашел этот связанный вопрос, но ответ там не дает реалистичного понимания - просто абстрактно говорит о коробках.
Ответы
Ответ 1
Нам нужно быть осторожным, чтобы не смешивать концепции здесь.
Во-первых, там подтипирование. int
является подтипом object
. Подтип в основном означает, что контракт гарантирован супертипом (например, "Существует метод ToString, который возвращает подходящее представление строки." ) Также гарантируется для подтипа.
Тогда существует наследование в С#. В С# наследование
-
создает подтип, гарантируя, что интерфейс, предоставленный супертипом, также доступен в подтипе и
-
предоставляет реализации по умолчанию, т.е. если вы не переопределяете метод, вы получаете реализацию супертипа. Это в основном удобная функция.
( реализация интерфейса в С# будет примером для другого механизма подтипирования, который обеспечивает 1, но не 2.)
Это в основном все. Ни подтипирование, ни наследование не гарантируют макеты памяти, семантику значения/ссылочного типа и т.д. Концепции ортогональны.
"Но это не так", - можете сказать вы. "Часть контракта object
- это семантика ссылочного типа". Именно здесь необходим бокс. Он имитирует семантику ссылочного типа всякий раз, когда тип времени компиляции типа значения является ссылочным (т.е. object
, ValueType
или интерфейс).
Ответ 2
Мы знаем, что все типы происходят от Object. который является ссылочным типом. мой вопрос в том, почему int - который является типом значения - наследуется от ссылки тип объекта? Возможно ли это?
System.Int32
происходит от System.ValueType
, как и все структуры на С#. Эта цепочка наследования разрешена компилятором, который является тем же механизмом, который запрещает вам наследование в любом другом типе struct
. Общая среда исполнения CLR имеет специальную семантику для типов, которые выводятся из System.ValueType
. System.ValueType
сам по себе не является типом значения, он ссылочный тип, который формирует базовый класс для всех структур. Хотя эта иерархия наследования существует, она не должна гарантировать ничего о том, как объекты будут изложены в памяти.
почему нам нужно поле при передаче функции int, которая ожидает объект как параметр? Обычно со ссылками, когда вам нужно передать объект производный тип в качестве параметра для функции ожидающего объекта базового типа, вам не нужно ничего делать. Почему здесь поле?
Потому что хотя любой struct
в конечном итоге происходит от object
, он фактически обрабатывается по-разному во время выполнения. Все структуры рассматриваются как блок данных, у них нет указателя таблицы методов или индекса блока синхронизации, который имеет каждый ссылочный тип в .NET. Вот почему любой тип значения, который передается методу, принимающему object
, должен быть помещен в бокс, потому что дополнительные данные должны быть добавлены к нему, чтобы фактически стать полностью квалифицированным типом object
. Типы значений не только помещаются в бокс, когда они передаются как типы object
, они также помещаются в бокс, например, когда вы вызываете метод, который был добавлен в ваш struct
в результате реализации интерфейса. Этот тип значения должен быть помещен в бокс, чтобы получить фактический указатель таблицы метода к методу, который необходимо вызвать.
Вы можете увидеть это с помощью небольшого примера:
void Main()
{
IFoo m = new M();
m.X();
}
public struct M : IFoo
{
public void X() { }
}
public interface IFoo
{
void X();
}
Выдает следующий IL (скомпилированный в режиме Release):
IL_0000: ldloca.s 00
IL_0002: initobj UserQuery.M
IL_0008: ldloc.0
IL_0009: box UserQuery.M
IL_000E: callvirt UserQuery+IFoo.X
IL_0013: ret
Ответ 3
Типы значений либо распределены по стекам, либо выделены встроенными в структуру. Типы ссылок выделены в виде кучи. Оба типа ссылок и значений производятся из конечного базового класса Object. В тех случаях, когда тип значения должен действовать как объект, оболочка, которая делает тип значения похожим на ссылочный объект, распределяется по куче, и значение типа значения копируется в него. Оболочка помечена так, что система знает, что она содержит тип значения. Этот процесс известен как бокс, а обратный процесс называется распаковкой. Бокс и распаковка позволяют любому типу обрабатываться как объект.