воскресенье, 18 сентября 2011 г.

Упаковка типов значения в CLR. Задачка для собеседования

Когда-то, прочитав книгу Джеффри Рихтера (Jeffrey Richter) CLR via C#, мне понравился пример из параграфа про упаковку и распаковку типа значения. На днях, вновь про него вспомнив, я решил посвятить ему первую запись в блоге.

Итак, пример, посмотрите внимательно на следующий код и подумайте, сколько операций упаковки будет произведено?
public static void Main()
{
int x = 5;
object y = x;
x = 10;

// вывести на консоль "10, 5"
Console.WriteLine(x + ", " + (int)y);
}
Ну как, посчитали? Тогда сверяем ответы, правильный ответ три операции упаковки. Вы насчитали столько же? Поздравляю! Нет? Что ж давайте разбираться. Думаю, что с четвертой строкой проблем не возникло ни у кого. Действительно, здесь значение переменной x копируется в управляемую кучу (managed heap) и адрес нового объекта присваивается y. Две других операции упаковки (с одной распаковкой) выполняются для вызова метода WriteLine. Метод ожидает передачи строки (объекта типа System.String). Поэтому три объекта: x (System.Int32), “, “ (System.String) и (int)y (System.Int32),- должны быть преобразованы в один. Для создания строки компилятор C# генерирует вызов статического метода Concat класса System.String, принимающего 3 параметра типа System.Object. Поэтому, переменная x упаковывается в System.Object (вторая операция упаковки), строка “, “ передается как ссылка на объект System.String, а только что распакованная переменная y, вновь упаковывается (третья операция упаковки). Чтобы не быть голословным приведу пример сгенерированного IL кода:
.method public hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 45 (0x2d)
.maxstack 3
.locals init ([0] int32 x,
[1] object y)
// загрузка 5 в x
IL_0000: ldc.i4.5
IL_0001: stloc.0

// упаковка x и сохранение ссылки на упакованный объект в y
IL_0002: ldloc.0
IL_0003: box [mscorlib]System.Int32
IL_0008: stloc.1

// изменение неупакованной переменной x
IL_0009: ldc.i4.s 10
IL_000b: stloc.0

// упаковка x и размещение указателя в стеке для вызова метода Concat
IL_000c: ldloc.0
IL_000d: box [mscorlib]System.Int32

// загрузка строки “, “ в стек для вызова метода Concat
IL_0012: ldstr ", "

// распаковка переменной y
IL_0017: ldloc.1
IL_0018: unbox.any [mscorlib]System.Int32

// упаковка только что распакованной переменной
// и размещение указателя на неё в стеке для вызова метода Concat
IL_001d: box [mscorlib]System.Int32

// вызов метода Concat
IL_0022: call string [mscorlib]System.String::Concat(object,
object,
object)

// передача полученной строки в метод WriteLine
IL_0027: call void [mscorlib]System.Console::WriteLine(string)
IL_002c: ret
} // end of method Program::Main
Как изменить этот пример, чтобы свести количество операций упаковки и распаковки к минимуму? Замена восьмой строки на
Console.WriteLine(“{0}, {1}”, x, (int)y)
не поменяет кардинально ситуацию. Количество операций упаковки так же останется три (плюс одна операция распаковки). Только в данном случае компилятор не будет генерировать последовательный вызов методов Concat и WriteLine, а сгенерирует вызов одного метода WriteLine принимающего строку (System.String) и два объекта типа System.Object. Естественно для них понадобится упаковка.

Но ключ для решения задачи все в той же восьмой строке. Чтобы количество операций упаковки и распаковки было минимальным, достаточно для переменной x явно вызвать метод получения строки ToString, а переменную y не распаковывать:
Console.WriteLine(x.ToString() + “, “ + y)
. В таком случае останется только одна операция упаковки (четвертая строка).

4 комментария:

  1. Задача хорошая, пришлось как следует подумать, чтобы правильно ответить)
    Но не для собеседования уж точно.

    ОтветитьУдалить
  2. >> x + ", " + (int) y
    В качестве ответа на собеседовании подошло бы «Нужно смотреть IL и сигнатуру метода, который вызывается для конкатенации строк»?

    ОтветитьУдалить
  3. Игорь, вы правы задача запутанная, но в то же время есть о чем побеседовать ;)

    Роман, да начало разговора хорошее! Ведь важно понять, что кандидат умеет рассуждать, строить логические выводы. Собеседующий мог бы предложить различные сигнатуры метода и порассуждать как они будут влиять на количество упаковок.

    ОтветитьУдалить
  4. Гораздо полезнее было бы "посветить" затраченное время на изучение грамматике русской езыка.

    ОтветитьУдалить