Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

Сравнение типов значений с null

Самый простой пример - метод Add() класса Dictionary. При добавлении ключа, проверяется равен ли он null и для этого параметр key приводится к object на тот вариант, если TKey значимый тип. Однако меня интересует целесообразность упаковки valueType, ведь условие if(key == null){} будет работать исправно в любом случае. Т. о. если я правильно понимаю, приведение к object нужно лишь для того чтобы не получить предупреждение? upd. Скит пишет int i = 5; if (i == null) { Console.WriteLine ("Never going to happen"); } Компилятор С# выдаст для этого кода предупреждение, но вас может удивить, что он допустим вообще. Что же здесь происходит, действительно ли компилятор видит выражение int в левой стороне оператора ==, видит значение null в правой стороне и знает, что нужно неявное преобразование в тип int? для каждого из них. Поскольку сравнения между двумя значениями типа int? совершенно допустимы, код не вызывает ошибки, только предупреждение. В итоге, если мы упакуем значение вот так: int i = 5; if ((object)i == null) { Console.WriteLine ("Never going to happen"); } То получим новый объект в куче, которого в принципе могло бы и не быть, если просто сравнивать значения.


Ответ

Немного подробностей - несмотря на то, что в исходниках действительно нет (object), оно есть в IL.
public static void Test(T param) { if (param == null) { Console.WriteLine("Never going to happen"); } }
после компиляции (в release!) в IL превращается в
.method public hidebysig static void Test(!!T param) cil managed { // Code size 19 (0x13) .maxstack 8 IL_0000: ldarg.0 IL_0001: box !!T <---- IL_0006: brtrue.s IL_0012 IL_0008: ldstr "Never going to happen" IL_000d: call void [mscorlib]System.Console::WriteLine(string) IL_0012: ret } // end of method Program::Test
Обратите внимание, что никаких вызовов == уже нет. Значение в стеке - адрес объекта в памяти - просто сравнивается с 0.
Более того, boxing и проверка на null есть и после прохода JIT по коду при запуске под отладчиком:
Test(5); 01212640 mov ecx,5 01212645 call dword ptr ds:[5191C7Ch]
----
if (param == null) 01212678 push ebp 01212679 mov ebp,esp 0121267B sub esp,8 0121267E xor eax,eax 01212680 mov dword ptr [ebp-8],eax 01212683 mov dword ptr [ebp-4],ecx 01212686 cmp dword ptr ds:[5190B94h],0 0121268D je 01212694 0121268F call 7431C310 01212694 mov ecx,73273B04h 01212699 call 01182100 0121269E mov dword ptr [ebp-8],eax 012126A1 mov eax,dword ptr [ebp-8] 012126A4 mov edx,dword ptr [ebp-4] 012126A7 mov dword ptr [eax+4],edx 012126AA mov eax,dword ptr [ebp-8] 012126AD test eax,eax 012126AF jne 012126BC { Console.WriteLine("Never going to happen"); 012126B1 mov ecx,dword ptr ds:[3D621F4h] 012126B7 call 731A023C } } 012126BC nop } } 012126BD mov esp,ebp 012126BF pop ebp 012126C0 ret
а вот запуск без отладчика полностью меняет картину. Код вида
Debugger.Launch(); Test(5); Test("test");
на самом деле превращается в
Debugger.Launch(); 041D0050 push ebp 041D0051 mov ebp,esp 041D0053 call 7386868C Test("test"); 01260058 cmp dword ptr ds:[3C82188h],0 <-- это заинлайненный if 0126005F jne 01260076 01260061 call 731A0258
Нет никакого box. Нет if. Нет даже ни одного вызова Test. Вызов с 5 удален целиком. Вызов с string test - заинлайнен.
Не заморачивайтесь и дайте JIT делать свою работу. И да, компилируйте Production сборки как Release, а не как Debug. Это сокращает жизнь переменных - и продлевает жизнь разработчика.

Комментариев нет:

Отправить комментарий