Страницы

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

суббота, 30 ноября 2019 г.

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

#c_sharp #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"); 
}

То получим новый объект в куче, которого в принципе могло бы и не быть, если просто
сравнивать значения.    


Ответы

Ответ 1



Немного подробностей - несмотря на то, что в исходниках действительно нет (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. Это сокращает жизнь переменных - и продлевает жизнь разработчика.

Ответ 2



Если вы действительно хотите сравнить с null, то сравнивайте на здоровье. Это предупреждение существует, так как подобное сравнение часто ошибочно. Будете ли вы приводить к object или сравнивать просто так — наверняка не имеет значения, потому что JIT-компилятор выкинет избыточные сравнения с null для value-типов. В исходнике CoreCLR простое сравнение: private int FindEntry(TKey key) { if( key == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key); } // ... }

Ответ 3



Т. о. если я правильно понимаю, приведение к object нужно лишь для того чтобы не получить предупреждение? Подозреваю, что неправильно. Как уже написали, в исходниках этого приведения нет. Я думаю, что оно появляется в скомпилированном варианте в силу того, что generic-методы компилируются на основе информации, которая известна при компиляции, а не при запуске. Соответственно, там дан только generic-тип TKey, на который не накладывается никаких ограничений. Всё, что знает компилятор, что это некоторый object. Дальше он наталкивается на использование оператора ==, единственная применимая перегрузка которого принимает два объекта object. Чтобы вызвать этот оператор, key неявно приводится компилятором к object. Кстати, весьма вероятно, что jit-компилятор всё равно оптимизирует эту штуку, как происходит с кастом в IEnumerable при указании типа переменной в linq-запросе. PS: Это только мои представления о происходящем, я не проверял, действительно ли всё происходит именно так.

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

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