3

Рассмотрим этот фрагмент IL (который был создан с помощью C# компилятор от Microsoft):Почему виртуальные элементы System.Object вызываются в unboxed типах значений (которые не имеют базового класса)?

.class public sequential ansi sealed beforefieldinit Foo 
     extends [mscorlib]System.ValueType 
{ … } 

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .maxstack 1 
    .locals init ([0] valuetype Foo foo) 

    ldloca.s   foo             // ? 
    constrained. Foo              // ? 
    callvirt   instance string [mscorlib]System.Object::ToString() // ? 
    pop 
    ret 
} 

Я хотел бы точно знать, что происходит в трех линиях отмечен // ?: Как это возможно, что виртуальный метод (System.ObjectToString) вызывается в unboxed тип значения, который (согласно разделу I.8.9.7 спецификации CLI) вообще не имеет базового типа?

Моего текущее, неполное понимание таково:

  • ldloca.s foo приводит к транзиторному указателю (*) к локальным переменным foo (который содержит Unboxed значения типа valuetype Foo), который в этом случае, в соответствии с раздел I.12.3.2.1 спецификации CLI, может использоваться там, где ожидаются управляемые указатели (&).

  • Этот указатель * будет действовать как этот указатель для вызова метода. Это представляется законным, поскольку он может действовать как управляемый указатель (&) здесь. В стандарте CLI упоминается эта возможность в разделе I.8.9.7.

  • Префикс constrained. Foo для предотвращения бокса valuetype Foo значение boxed Foo объект.

Но остается основной вопрос: почему виртуальный метод может быть вызван на unboxed значение, которое не наследует этот виртуальный метод?

ответ

3

Как это возможно, что виртуальный метод вызывается System.Object.ToString от типа Unboxed значения, которое (в соответствии с разделом I.8.9.7 спецификаций CLI) не имеет базовый типа вообще?

Я смущен вопросом. Что связано с наличием или отсутствием базового типа?

Я хотел бы точно знать, что происходит в трех линиях

Ключ constrained префикс. Документация - Раздел III, раздел 2.1 - довольно проста. В документации у нас есть тип приемника thisType, управляемого указателя на этот тип ptr и constrained.callvirt от method.Правила:

  1. Если thisType является ссылочным типом, то ptr разыменовывается и передается как this указатель на callvirt из method
  2. Если thisType является типом значения и thisType реализует method затем ptr передается немодифицированный как указатель this на call от method, осуществленный thisType
  3. Если thisType представляет собой тип значения и thisType не реализует метод затем ptr разыменовывается, штучной упаковке, и передается как this указатель на callvirt из method

В вашем примере точки (3) применяется. Тип Foo является типом значения, он не реализует метод ToString, поэтому он помещается в коробку, и метод (предоставляемый базовым классом) вызывается со ссылкой на поле как this.

Предположим, что у нас была int.ToString. Тогда применяется точка (2). Тип int, это тип значения, а int реализует переопределение System.Object.ToString(). Таким образом, управляемый указатель на int становится this вызова ToString. Таким образом, излишне бокс. (А если ToString мутировали int тогда мутация будет происходить на переменной заданного в качестве приемника, а не на коробочную копию.)

Почему виртуальный метод можно назвать на Unboxed значение, которое не наследуется, что виртуальная метод?

Вопрос под рукой метод, является ли или не реализован, как называется в документации, которую я цитировал выше. Что наследует от этого наследство?

вопрос вы не просили, но это хорошее место, чтобы ответить на него:

Должен ли я всегда осуществлять ToString на моих типов значений?

Ну, я не знаю, о всегда, но это, конечно, хорошая идея, чтобы сделать это, потому что (1) реализация по умолчанию ToString ничтожен, и (2), реализовав его на значение типы, которым вы можете удалять штраф за бокс при каждом вызове метода.

То же самое касается других виртуальных методов объекта?

Да. И есть веские причины сделать ваше собственное равенство и хеширование в значениях типов в любом случае. По умолчанию значение равенства по умолчанию может быть неожиданным.

Отмечу, что GetType не является виртуальным. Это уместно?

Да; не являясь виртуальным средством, он не может быть переопределен в типе значения, а это означает, что вызов GetType по любому типу значений всегда включает его. Конечно, если у вас есть тип unboxed value in hand, то вам не нужно звонить GetType, потому что вы уже знаете, что его тип во время компиляции!

+0

Если я правильно вас понимаю, то (3) применим в моем вопросе: Я вызываю 'foo.ToString()' (если вы разрешите мне это краткое использование синтаксиса C#). 'foo' имеет тип' valuetype Foo', который является типом значения и не реализует 'ToString' (потому что я сам не определил этот метод, и у' valuetype Foo' нет никакого базового типа, из которого он мог бы наследовать 'ToString '). Таким образом, в соответствии с тем, что вы говорите, значение в 'foo' помещается в квадрат в значение типа' boxed Foo', которое наследуется от 'System.ValueType', которое имеет реализацию' ToString'. Вы согласны? – stakx

+0

@stakx: Упс, я неправильно читаю ваш пример. Я уточню свой ответ. –

+0

@stakx: Да, ты подвел итог. –