Итак, пример, посмотрите внимательно на следующий код и подумайте, сколько операций упаковки будет произведено?
public static void Main()Ну как, посчитали? Тогда сверяем ответы, правильный ответ три операции упаковки. Вы насчитали столько же? Поздравляю! Нет? Что ж давайте разбираться. Думаю, что с четвертой строкой проблем не возникло ни у кого. Действительно, здесь значение переменной 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 кода:
{
int x = 5;
object y = x;
x = 10;
// вывести на консоль "10, 5"
Console.WriteLine(x + ", " + (int)y);
}
.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). В таком случае останется только одна операция упаковки (четвертая строка).
Задача хорошая, пришлось как следует подумать, чтобы правильно ответить)
ОтветитьУдалитьНо не для собеседования уж точно.
>> x + ", " + (int) y
ОтветитьУдалитьВ качестве ответа на собеседовании подошло бы «Нужно смотреть IL и сигнатуру метода, который вызывается для конкатенации строк»?
Игорь, вы правы задача запутанная, но в то же время есть о чем побеседовать ;)
ОтветитьУдалитьРоман, да начало разговора хорошее! Ведь важно понять, что кандидат умеет рассуждать, строить логические выводы. Собеседующий мог бы предложить различные сигнатуры метода и порассуждать как они будут влиять на количество упаковок.
Гораздо полезнее было бы "посветить" затраченное время на изучение грамматике русской езыка.
ОтветитьУдалить