Почему компилятор C # выдает инструкцию callvirt для вызова метода GetType()?

StackOverflow https://stackoverflow.com/questions/845657

Вопрос

Мне любопытно узнать, почему это происходит.Пожалуйста, прочтите приведенный ниже пример кода и соответствующий IL, который был указан в комментариях под каждым разделом:

using System;

class Program
{
    static void Main()
    {
        Object o = new Object();
        o.GetType();

        // L_0001: newobj instance void [mscorlib]System.Object::.ctor()
        // L_0006: stloc.0 
        // L_0007: ldloc.0 
        // L_0008: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()

        new Object().GetType();

        // L_000e: newobj instance void [mscorlib]System.Object::.ctor()
        // L_0013: call instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
    }
}

Почему компилятор выдал callvirt для первого раздела, но a call для второго раздела?Есть ли какая-либо причина, по которой компилятор когда-либо выдавал callvirt инструкция для невиртуального метода?И если есть случаи, в которых компилятор будет выдавать callvirt для невиртуального метода это создает проблемы с безопасностью типов?

Это было полезно?

Решение

Просто перестраховываюсь.

Технически компилятор C # этого не делает всегда использование callvirt

Для статических методов и мето-дов, определенных для типов значений, он использует call.Большая часть предоставляется через callvirt IL instruction.

Разница, которая повлияла на голосование между этими двумя, заключается в том факте, что call предполагается, что "объект, используемый для выполнения вызова", не равен null. callvirt с другой стороны, проверяет наличие not null и при необходимости выдает исключение NullReferenceException.

  • Для статических методов объект является типом object и не может быть null.То же самое для типов значений.Следовательно call используется для них - лучшая производительность.
  • Что касается остальных, разработчики языка решили использовать callvirt таким образом, JIT-компилятор проверяет, что объект, используемый для выполнения вызова, не равен null.Даже для методов невиртуального экземпляра..они ценили безопасность выше производительности.

Смотрите также:Джефф Рихтер лучше справляется с этим - в своей главе "Проектирование типов" в CLR через C # 2nd Ed

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

Видишь это старая запись в блоге Эрика Ганнерсона.

Вот текст поста:

Почему C # всегда использует callvirt?

Этот вопрос возник из-за внутреннего псевдонима C #, и я подумал, что ответ будет представлять общий интерес.Это при условии, что ответ правильный - прошло довольно много времени.

Язык .NET IL предоставляет как инструкцию call, так и callvirt, причем callvirt используется для вызова виртуальных функций.Но если вы просмотрите код, который генерирует C #, вы увидите, что он генерирует "callvirt" даже в тех случаях, когда не задействована виртуальная функция.Почему он это делает?

Я снова просмотрел имеющиеся у меня заметки по языковому дизайну, и в них совершенно четко указано, что мы решили использовать callvirt 13.12.1999.К сожалению, они не отражают наше обоснование для этого, поэтому мне придется руководствоваться своей памятью.

Мы получили отчет от кого - то (вероятно , одного из .СЕТЕВЫЕ группы, использующие C # (думали, что в то время он еще не назывался C #)), которые написали код, вызывающий метод по нулевому указателю, но они не получили исключения, потому что метод не обращался ни к каким полям (т. Е. “this” было null, но ничто в методе его не использовало).Затем этот метод вызвал другой метод, который использовал этот пункт и выдал исключение, после чего последовало небольшое недоумение.После того, как они разобрались в этом, они прислали нам записку об этом.

Мы подумали, что возможность вызвать метод в нулевом экземпляре была немного странной.Питер Голд провел некоторое тестирование, чтобы увидеть, каково влияние постоянного использования callvirt на производительность, и оно было достаточно небольшим, чтобы мы решили внести изменения.

Как (возможно -) интересное в стороне... GetType() необычен тем , что он это не так virtual - это приводит к некоторым очень, очень странные вещи.

(помечено как wiki, поскольку это несколько не соответствует теме самого вопроса)

Компилятор не знает реального типа o в первом выражении, но он знает реальный тип во втором выражении.Похоже, что он рассматривает только одно утверждение за раз.

Это нормально, потому что C # сильно зависит от JIT для оптимизации.В таком простом случае очень вероятно, что оба вызова станут вызовами экземпляра во время выполнения.

Я не верю callvirt когда-либо генерируется для невиртуальных методов, но даже если бы это было так, это не было бы проблемой, потому что метод никогда не был бы переопределен (по очевидным причинам).

Я бы рискнул предположить, что это потому, что первое присваивает переменной, которая потенциально может содержать сокращенный экземпляр другого типа, который мог бы быть переопределен GetType (даже несмотря на то, что мы видим, что это не так);второе никогда не могло быть ничем иным, кроме Object.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top