Как работает CLR при вызове метода структуры - PullRequest
7 голосов
/ 31 марта 2011

Я думаю, что я знаю ответ для класса, просто хочу подтвердить, что мое понимание верно. Допустим, у меня есть ClassA и его экземпляр с именем a. Когда a.MethodA() вызывается:

(1) CLR находит тип ClassA по указателю типа из a в куче (тип загружен в кучу)

(2) Найдите MethodA в типе, если он не найден, перейдите к его базовому типу, пока не появится класс object.

Может быть, мое понимание не совсем точное, но я думаю, что оно в основном правильно (поправьте меня, если оно ошибочно!). И тут встает вопрос о простой struct .

struct MyStruct
{
   public void MethodA() { }
}

У меня есть var x = new MyStruct();, его значение находится в стеке, а тип MyStruct загружен в кучу. При выполнении x.MethodA(), конечно, нет бокса. Как CLR найти MethodA, получить IL и выполнить / JIT? Я думаю, что ответ, вероятно: (снова, поправьте меня, если я не прав)

(1) у нас есть тип объявления из x в стеке. CLR находит его тип по информации в стеке и находит MethodA в своем типе. - давайте назовем это assumptionA.

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

А как насчет x.ToString() или x.GetType()? Мы знаем, что значение будет упаковано, и тогда оно будет работать как класс. Но зачем нам здесь бокс? Так как мы можем получить его тип (предположение A говорит нам), почему бы не перейти к его базовому типу и найти метод (как класс)? Зачем здесь дорогая коробка операций?

Ответы [ 3 ]

4 голосов
/ 31 марта 2011

Предположение А неверно.Таблица символов компилятора C # хранит информацию о типах.Эта статическая информация о типе используется почти во всех случаях, динамический тип, хранящийся в объекте, необходим только во время проверок типов (оператор is), приведения (оператор as и фактический синтаксис приведения) и дисперсии массива, и только затемкогда динамический тип не известен компилятору.Динамический тип распакованной структуры всегда статически известен, а динамический тип экземпляра класса статически известен около экземпляра и внутри условного блока, который выполнил проверку типа (например, в if (x is T) y = (T)x; тип известен внутри тогдашней частипоэтому приведение не требует другой динамической проверки).

Хорошо, теперь, поскольку компилятор C # статически знает тип x, он может выполнить разрешение перегрузки и найти точный Метод А вызывается.Затем он отправляет MSIL для переноса аргументов в виртуальный стек MSIL и выдает инструкцию вызова, содержащую ссылку на метаданные для этого конкретного метода.Во время выполнения проверки типов не требуются.

Для x.ToString() компилятор C # по-прежнему знает точный метод, который он хочет вызвать.Если ToString был переопределен типом struct, он ожидает параметр типа pointer-to- MyStruct, который компилятор обрабатывает без упаковки.Если ToString не было переопределено, компилятор генерирует вызов Object.ToString, который ожидает объект в качестве своего параметра.Чтобы вставить x в виртуальный стек MSIL, поскольку для правильного типа требуется бокс.

GetType - это особый случай, когда тип известен статически, компилятор не будет вызывать какой-либо метод, он просто получитвведите информацию из таблицы символов и вставьте ссылку на метаданные непосредственно в MSIL.

4 голосов
/ 31 марта 2011

Ну, здесь происходит несколько разных вещей:

  • Для методов, которые определены в структуре , CLR просто смотрит на определение типа в метаданных сборки при его загрузке, чтобы выяснить, что это за методы, и когда метод Foo вызывает MethodA, CLR просто связывается с правильным методом , когда MethodA равен JIT'd . После того, как компиляция состоялась, больше ничего не происходит; метод вызывается напрямую, потому что любая необходимая информация уже присутствует.

  • Для виртуальных унаследованных методов struct, таких как ToString, там имеет для бокса, потому что виртуальные вызовы могут быть вызваны только на Object с, по замыслу - без бокса нет v-таблицы, в которую можно было бы взглянуть, чтобы выяснить результирующий метод. (Тот факт, что вызов метода может быть сразу после бокса , может учесть потенциальную оптимизацию, но это долгий путь - я сомневаюсь, что JIT-компилятор делает это.) очевидно, нет никакого бокса; Я был неправ, потому что я не заметил, что эти методы переопределены. Действительно, для переопределенных методов компилятор выполняет оптимизацию, просто вызывая метод напрямую, потому что нет причин этого не делать. (Существуют нет виртуальных методов для типов значений, которые не переопределяются, так что на самом деле это не проблема.)

  • Для не виртуальных struct методов, которые наследуются , объект должен быть упакован просто потому, что метод по определению вызывается для ссылочного типа, не по типу значения; в компиляторе нет особого случая, потому что я считаю, что JIT-компилятор действительно может выполнять оптимизацию (например, избегать упаковок), когда он использует JIT-метод, такой как GetType (хотя кто-то, пожалуйста, исправьте меня, если я ошибаюсь это вещь оптимизации).

0 голосов
/ 31 марта 2011

РЕДАКТИРОВАТЬ: Спасибо за комментарии.Я думал, что понимаю, как это работает ... больше нет.Поэтому я оставлю это как отправную точку для исследования, но не как ответ.

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

С другой стороны, как указано в комментариях, виртуальные функции из базового класса нуждаются в Object в качестве параметра "this".

С третьей стороны, глядя на сгенерированный IL, неясно, действительно ли ToString и GetHashCode делают бокс (также он где-то спрятан, поскольку здесь есть комментарий о боксе http://blogs.msdn.com/b/lucabol/archive/2007/12/24/creating-an-immutable-value-object-in-c-part-iii-using-a-struct.aspx). GetType определенно требует явного бокса.

Просматривая выходные данные ILDasm, чтобы увидеть, есть ли бокс или прямой вызов:

       int v = 42;

        string s = v.ToString();

        object a = v;
        s = a.ToString();

Получить скомпилированный (отладочный) в следующий IL. Нет бокса для int.ToString (), но определенно для приведения к объекту ...

  IL_0001:  ldc.i4.s   42
  IL_0003:  stloc.0
  IL_0004:  ldloca.s   v
  IL_0006:  call       instance string [mscorlib]System.Int32::ToString()
  IL_000b:  stloc.1
  IL_0013:  ldloc.0
  IL_0014:  box        [mscorlib]System.Int32
  IL_0019:  stloc.2
  IL_001a:  ldloc.2
  IL_001b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0020:  stloc.1
...