Другим случаем, когда NullReferenceExceptions
может случиться, является (неправильное) использование оператора as
:
class Book {
public string Name { get; set; }
}
class Car { }
Car mycar = new Car();
Book mybook = mycar as Book; // Incompatible conversion --> mybook = null
Console.WriteLine(mybook.Name); // NullReferenceException
Здесь Book
и Car
являются несовместимыми типами; a Car
не может быть преобразован / передан в Book
. Когда этот сбой завершается неудачно, as
возвращает null
. Используя mybook
после этого, вы вызываете NullReferenceException
.
В общем случае вы должны использовать cast или as
, как показано ниже:
Если вы ожидаете преобразования типа в всегда преуспевает (т. е. вы знаете, какой объект должен быть впереди времени), тогда вы должны использовать cast:
ComicBook cb = (ComicBook)specificBook;
Если вы не уверены в типе, но хотите попробовать , чтобы использовать его как определенный тип, затем используйте as
:
ComicBook cb = specificBook as ComicBook;
if (cb != null) {
// ...
}
Компилятор может создавать таблицы перехода, где это применимо. Например, когда вы используете рефлектор для просмотра кода, вы увидите, что для огромных переключателей на строках компилятор будет генерировать код, который использует хэш-таблицу для отправки этих данных. Хэш-таблица использует строки как ключи и делегирует кодам case
как значения.
У этого есть асимптотическое лучшее время выполнения, чем много цепей if
, и на самом деле быстрее даже для относительно небольшого количества строк.
Статистика отсутствия соответствия может быть не очень хорошей.
Если вы действительно загружаете источник, то значения совпадения, как известно, равны 21, как в случае if, так и в switch. Компилятор должен уметь абстрагироваться, зная, какой оператор должен выполняться в любое время, и процессор должен иметь возможность правильно предсказать ветку.
Более интересный случай - когда не каждый случай ломается, в моем но это, возможно, не было предметом эксперимента.
Конрад правильный. В случае включения непрерывных диапазонов целых чисел (например, где у вас есть случай 0, случай 1, случай 2 .. случай n), компилятор может сделать что-то еще лучше, потому что ему даже не нужно создавать хеш-таблицу; он просто хранит массив указателей на функции и, таким образом, может загружать свою цель перехода в постоянное время.
Это код для микроконтроллера PIC18 на языке C:
void main() {
int s1='0';
int d0;
int d1;
//if (s1 == '0') {d1 = '0'; d0 = '0';}
//else if (s1 == '1') {d1 = '0';d0 = '1';}
//else if (s1 == '2') {d1 = '1';d0 = '0';}
//else if (s1 == '3') {d1 = '1';d0 = '1';}
switch (s1) {
case '0': {d1 = '0';d0 = '0';} break;
case '1': {d1 = '0';d0 = '1';} break;
case '2': {d1 = '1';d0 = '0';} break;
case '3': {d1 = '1';d0 = '1';} break;
}
}
С ifs
s1='0' - 14 cycles
s1='1' - 21 cycles
s1='2' - 28 cycles
s1='3' - 33 cycles
s1='4' - 34 cycles
С помощью случаев
s1='0' - 17 cycles
s2='1' - 23 cycles
s3='2' - 29 cycles
s4='3' - 35 cycles
s5='4' - 32 cycles
So i может предположить, что на очень низком уровне ifs быстрее. Код в ПЗУ также короче.
Как сказал Конрад, компилятор может построить таблицу Jump.
В C ++ причина, по которой это возможно, связана с ограничением коммутаторов.
Это небольшое упрощение, как обычно, любой современный компилятор, который встречает последовательность if..else if ..
, которая может быть тривиально преобразована в оператор switch человеком, компилятор также будет. Но просто для того, чтобы добавить дополнительную забаву, компилятор не ограничен синтаксисом, поэтому он может генерировать «переключающие», подобные заявления внутри, которые имеют сочетание диапазонов, одиночных целей и т. Д., И они могут (и делать) делать это как для коммутатора, так и для. .else.
Anyhoo, расширение ответа Konrad заключается в том, что компилятор может генерировать таблицу переходов, но это не обязательно гарантировано (и не желательно). По множеству причин таблицы переходов приводят плохие вещи к предсказателям отрасли на современных процессорах, а сами таблицы плохо работают с кэшем, например.
switch(a) { case 0: ...; break; case 1: ...; break; }
Если компилятор действительно создал таблицу переходов для это, скорее всего, будет медленнее, чем альтернативный код стиля if..else if..
из-за того, что таблица переходов отклоняет предсказание ветвей.
Единственное преимущество случая if over - это когда наблюдается заметное увеличение частоты появления первого случая.
Не уверен, где именно находится порог, но я использую синтаксис case, если первый «почти всегда» не проходит первый тест.
Операторы switch / case могут быть обычно более быстрыми на 1 уровне, но когда вы начинаете получать 2 или более, инструкции switch / case начинаются в 2-3 раза до тех пор, пока вложенные операторы if / else.
В этой статье приведены некоторые сравнения скорости , выделяющие различия в скорости, когда такие операторы вложены.
Например, в соответствии с их тестами пример кода выглядит следующим образом:
if (x % 3 == 0)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 1)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else if (x % 3 == 2)
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
else
if (y % 3 == 0)
total += 3;
else if (y % 3 == 1)
total += 2;
else if (y % 3 == 2)
total += 1;
else
total += 0;
закончил в half время, в течение которого выполнялся эквивалентный оператор switch / case:
switch (x % 3)
{
case 0:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 1:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
case 2:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
default:
switch (y % 3)
{
case 0: total += 3;
break;
case 1: total += 2;
break;
case 2: total += 1;
break;
default: total += 0;
break;
}
break;
}
Да, это рудиментарный пример, но он иллюстрирует суть.
Таким образом, вывод может заключаться в использовании switch / case для простых типов, которые только на одном уровне, но для более сложных сравнений и множественных вложенных уровней используются классические конструкции if / else?