Почему типы значений наследуются от ссылочных типов?

У меня есть два вопроса:

  • Мы знаем, что все типы получены из 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. В тех случаях, когда тип значения должен действовать как объект, оболочка, которая делает тип значения похожим на ссылочный объект, распределяется по куче, и значение типа значения копируется в него. Оболочка помечена так, что система знает, что она содержит тип значения. Этот процесс известен как бокс, а обратный процесс называется распаковкой. Бокс и распаковка позволяют любому типу обрабатываться как объект.