Есть ли существенная разница между использованием if / else и switch-case в C #?

219

В чем польза / минус использования switchоператора по сравнению с if/elseC #. Я не могу представить, что есть такая большая разница, кроме, возможно, внешнего вида вашего кода.

Есть ли какая-то причина, по которой результирующий IL или связанная с ним производительность во время выполнения будут радикально отличаться?

Связанный: Что быстрее, включить строку или еще тип?

Мэтью М. Осборн
источник
Связанная тема: stackoverflow.com/questions/335971/is-switch-case-always-wrong
Джон Т
3
Этот вопрос интересен только теоретически для большинства разработчиков, если только вы часто не повторяете ОДИН МИЛЛИАРД. (Затем используйте оператор switch и переходите от 48 к 43 секундам ...) Или словами Дональда Кнута: «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла» en.wikipedia.org/wiki/Program_optimization#When_to_optimize
Отец
Я часто использую if / else вместо switch из-за шаткой общей области видимости switch.
Джош

Ответы:

341

Оператор SWITCH производит только ту же сборку, что и IF в режиме отладки или совместимости. В выпуске он будет скомпилирован в таблицу переходов (через оператор MSIL 'switch') - это O (1).

C # (в отличие от многих других языков) также позволяет включать строковые константы - и это работает немного по-другому. Очевидно, что создавать таблицы переходов для строк произвольной длины непрактично, поэтому чаще всего такой переключатель будет скомпилирован в стек IF.

Но если число условий достаточно велико, чтобы покрыть накладные расходы, компилятор C # создаст объект HashTable, заполнит его строковыми константами и выполнит поиск в этой таблице, а затем выполнит переход. Поиск Hashtable не является строго O (1) и имеет заметные постоянные затраты, но если количество меток падежа велико, это будет значительно быстрее, чем сравнение с каждой строковой константой в IF.

Подводя итог, если число условий больше 5 или около того, предпочитайте SWITCH, чем IF, в противном случае используйте то, что выглядит лучше.

ИМА
источник
1
Вы уверены, что компилятор C # создаст хеш-таблицу? Пункт, который я высказал о хеш-таблице в нашем обсуждении комментариев, касался собственных компиляторов, а не компилятора C #. Какой порог используется компилятором C # для создания хеш-таблицы?
Скотт Вишневски
8
Около десяти я думаю. 20, чтобы быть на безопасной стороне. И между прочим, мой гнев не на вас, а на людей, которые голосуют и принимают.
Има
48
Немного экспериментов предлагает подсчет <= 6: «если»; count> = 7: словарь. Это с компилятором MS .NET 3.5 C # - он может меняться между версиями и поставщиками, конечно.
Джон Скит
37
Эй, я должен извиниться перед тобой. Извините за то, что я кость.
Скотт Вишневски
Как практическое применение, есть ли разница в реальном мире в большинстве случаев? Я нахожу операторы переключения в C # странными, их синтаксис совсем не похож на что-то еще, и я обнаружил, что они делают мой код менее читаемым, стоит ли беспокоиться об использовании операторов переключения, или я должен просто программировать с другими if, если только пришел вернуться и заменить их, если я столкнулся с узкими местами производительности?
Джейсон Мастерс
54

В целом (с учетом всех языков и всех компиляторов) оператор switch МОЖЕТ СОДЕРЖАТЬ, когда оператор if / else более эффективен, поскольку компилятору легко генерировать таблицы переходов из операторов switch. То же самое можно сделать для операторов if / else с учетом соответствующих ограничений, но это гораздо сложнее.

В случае C # это также верно, но по другим причинам.

С большим количеством строк есть существенное преимущество в производительности при использовании оператора switch, потому что компилятор будет использовать хеш-таблицу для реализации перехода.

При небольшом количестве строк производительность между ними одинакова.

Это связано с тем, что в этом случае компилятор C # не создает таблицу переходов. Вместо этого он генерирует MSIL, который эквивалентен блокам IF / ELSE.

Существует инструкция MSIL «оператор переключения», которая при соединении будет использовать таблицу переходов для реализации оператора переключения. Однако он работает только с целочисленными типами (этот вопрос касается строк).

Для небольшого числа строк компилятору более эффективно генерировать блоки IF / ELSE, чем использовать хеш-таблицу.

Когда я первоначально заметил это, я сделал предположение, что поскольку блоки IF / ELSE использовались с небольшим количеством строк, компилятор сделал то же самое преобразование для большого количества строк.

Это было НЕПРАВИЛЬНО. «IMA» был достаточно любезен, чтобы указать мне на это (ну ... он не был добр в этом, но он был прав, а я был неправ, что является важной частью)

Я также сделал предположение о том, что в MSIL отсутствует инструкция «switch» (я подумал, что если есть примитив switch, почему они не используют его с хэш-таблицей, поэтому не должно быть примитива switch. ...) Это было неправильно и невероятно глупо с моей стороны. И снова «IMA» указал мне на это.

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

Тем не менее, я сделал это вики-сообществом, потому что считаю, что не заслуживаю REP за то, что был неправ. Если у вас есть шанс, пожалуйста, проголосуйте за пост Има.

Скотт Вишневски
источник
3
В MSIL есть примитив-переключатель, и операторы c # компилируются в общий C-подобный поиск. При определенных обстоятельствах (целевая платформа, переключатели cl и т. Д.) Во время компиляции коммутатор может быть расширен до IF, но это только мера резервной совместимости.
Има
6
Все, что я могу сделать, это извиниться за глупую ошибку. Поверьте мне, я чувствую себя глупо по этому поводу. Если серьезно, я думаю, что это все еще лучший ответ. В нативных компиляторах возможно использовать хеш-таблицу для реализации перехода, так что это не какая-то ужасно неправильная вещь. Я сделал одну ошибку.
Скотт Вишневски
9
Има, если есть ошибки, укажите их. Похоже, Скотт будет рад исправить сообщение. Если нет, то другие, кто получил возможность исправить ответ, сделают это. Это единственный способ, с помощью которого такой сайт будет работать, и, похоже, в целом он работает. Или возьми свой мяч и иди домой :)
jwalkerjr
2
@ Скотт: Я бы посоветовал вам отредактировать второй и третий абзацы, чтобы явно указать «для строк». Люди могут не читать обновление внизу.
Джон Скит
4
@ima Если вы считаете, что ответ объективно неправильный, отредактируйте его, чтобы он был правильным. Вот почему каждый может редактировать ответы.
Майлз Рут
18

Три причины, чтобы предпочесть switch:

  • Компилятор, нацеленный на нативный код, часто может скомпилировать оператор switch в одну условную ветвь плюс косвенный переход, тогда как для последовательности ifs требуется последовательность условных ветвей . В зависимости от плотности дел было написано очень много научных статей о том, как эффективно составлять описания дел; некоторые связаны со страницей компилятора lcc . (У Lcc был один из самых инновационных компиляторов для коммутаторов.)

  • Оператор switch является выбором среди взаимоисключающих альтернатив, а синтаксис переключателя делает этот поток управления более прозрачным для программиста, чем набор операторов if-then-else.

  • В некоторых языках, включая, безусловно, ML и Haskell, компилятор проверяет, не были ли вы исключены какие-либо случаи . Я рассматриваю эту функцию как одно из главных преимуществ ML и Haskell. Я не знаю, может ли C # сделать это.

Анекдот: на лекции, которую он прочитал, получив награду за пожизненные достижения, я услышал, как Тони Хоар сказал, что из всего, что он делал в своей карьере, были три, которыми он больше всего гордился:

  • Изобретая Быструю сортировку
  • Изобретая оператор switch (который Тони назвал caseоператором)
  • Начиная и заканчивая карьеру в промышленности

Я не могу представить себе жизнь безswitch .

Норман Рэмси
источник
16

Компилятор собирается оптимизировать практически все в один и тот же код с небольшими отличиями (Кнут, кто-нибудь?).

Разница в том, что оператор switch является более чистым, чем пятнадцать операторов if, соединенных вместе.

Друзья не позволяют друзьям составлять операторы if-else.


источник
13
«Друзья не позволяют друзьям составлять операторы if-else». Вы должны сделать усыпальницу из бруса :)
Мэтью М. Осборн
14

На самом деле, оператор switch более эффективен. Компилятор оптимизирует его до таблицы поиска, где с помощью операторов if / else он не может. Недостатком является то, что оператор switch нельзя использовать со значениями переменных.
Вы не можете сделать:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}

должно быть

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}
kemiller2002
источник
1
у вас есть какие-либо номера? Мне любопытно, насколько хорошо компилятор может оптимизировать оператор swtich по сравнению с If / Else
Мэтью М. Осборн
да, я считаю, что оператор switch всегда оптимизируется до O (1), где оператор if else будет O (n), где n - позиция правильного значения в операторах if / else if.
kemiller2002
В случае с C # это не так, см. Мой пост ниже для получения дополнительной информации.
Скотт Вишневски
Я не совсем уверен в этом, но я не могу найти информацию в книге, клянусь, что нашел ее. Вы уверены, что не смотрите на код MSIL без оптимизации. Он не создаст таблицу переходов, если вы не скомпилируете с включенной оптимизацией.
kemiller2002
Я скомпилировал как в режиме отладки, так и в режиме розничной торговли, и в обоих случаях он генерирует блоки if / else. Вы уверены, что книга, которую вы просматривали, говорила о C #? Книга, вероятно, была либо книгой компилятора, либо книгой о C или C ++
Скотт Вишневски,
14

Я не видел, чтобы кто-то еще поднимал (очевидный?) Вопрос о том, что предполагаемое преимущество в эффективности оператора switch зависит от того, насколько разные случаи примерно одинаково вероятны. В случаях, когда одно (или несколько) значений гораздо более вероятны, лестница if-then-else может быть намного быстрее, гарантируя, что сначала проверяются самые распространенные случаи:

Так, например:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}

против

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}

Если x равен нулю 90% времени, код if-else может быть в два раза быстрее кода на основе коммутатора. Даже если компилятор превратит «переключатель» в какое-то умное движение на основе таблиц, это все равно будет не так быстро, как простая проверка на ноль.

Марк Бесси
источник
3
Нет преждевременной оптимизации! В общем, если у вас есть больше, чем несколько случаев, и они switchсовместимы, switchутверждение лучше (более читабельно, иногда быстрее). Если вы знаете, что один случай гораздо более вероятен, вы можете извлечь его, чтобы сформировать if- else- switchконструкцию, и, если он заметно быстрее , вы оставите это в. Если switchвыродится и станет слишком маленьким, регулярное выражение-замена выполнит большую часть работы по преобразованию его в else if-цепь.
никто не
6
Оригинальный вопрос (три года назад!) Только что спросил о преимуществах и недостатках между if / else и switch. Это один из примеров. Я лично видел, что такого рода оптимизация существенно влияет на время выполнения рутины.
Марк Бесси
7

часто это будет выглядеть лучше - то есть будет легче понять, что происходит. Учитывая, что выигрыш в производительности в лучшем случае будет крайне минимальным, наиболее важным отличием является представление кода.

Так что, если if / else выглядит лучше, используйте его, в противном случае используйте оператор switch.

gbjbaanb
источник
4

Побочная тема, но я часто переживаю (и чаще вижу) if/ elseи switchутверждение становится слишком большим со слишком большим количеством случаев. Они часто вредят ремонтопригодности.

Общие виновники включают в себя:

  1. Делать слишком много внутри нескольких операторов if
  2. Больше случаев, чем можно по-человечески проанализировать
  3. Слишком много условий в оценке if, чтобы знать, что ищется

Исправить:

  1. Извлечение в метод рефакторинга.
  2. Используйте словарь с указателями методов вместо регистра или используйте IoC для дополнительной настраиваемости. Методы фабрики также могут быть полезны.
  3. Извлеките условия своим методом
Крис Брандсма
источник
4

Согласно этой ссылке, сравнение IF и Switch итерационного теста с использованием оператора switch и if аналогично 1 000 000 000 итераций, время, затраченное оператором Switch = 43,0 с, и оператором If = 48,0 с.

Это буквально 20833333 итераций в секунду, так что, если мы действительно должны сосредоточиться больше,

PS: Просто чтобы узнать разницу в производительности для небольшого списка условий.

Bretfort
источник
Это для меня.
Грег Гам
3

Если вы просто используете оператор if or else, базовое решение использует сравнение? оператор

(value == value1) ? (type1)do this : (type1)or do this;

Вы можете сделать или рутина в выключатель

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}
Роберт В.
источник
2

На самом деле это не отвечает на ваш вопрос, но, учитывая, что между скомпилированными версиями будет мало различий, я бы настоятельно призвал вас написать свой код так, чтобы он наилучшим образом описывал ваши намерения. Мало того, что у компилятора больше шансов делать то, что вы ожидаете, но и другим будет проще поддерживать ваш код.

Если вы намерены разветвить свою программу на основе значения одной переменной / атрибута, то оператор switch лучше всего отражает это намерение.

Если вы намерены разветвлять вашу программу на основе различных переменных / атрибутов / условий, тогда цепочка if / else if лучше всего отражает это намерение.

Я признаю, что Коди прав в том, что люди забывают команду break, но почти так же часто я вижу, что люди делают сложные, если блоки, где они ошибаются в {}, так что строки, которые должны быть в условном выражении, - нет. Это одна из причин, по которой я всегда включаю {} в свои операторы if, даже если в нем есть одна строка. Мало того, что это легче читать, но если мне нужно добавить еще одну строку в условной, я не могу забыть добавить ее.

dj_segfault
источник
2

Интересный вопрос. Это возникло несколько недель назад на работе, и мы нашли ответ, написав фрагмент кода и просмотрев его в .NET Reflector (рефлектор потрясающий !! Мне очень нравится).

Вот что мы обнаружили: допустимый оператор switch для чего-либо, кроме строки, компилируется в IL как оператор switch. Однако, если это строка, она переписывается как if / else if / else в IL. Поэтому в нашем случае мы хотели узнать, как операторы switch сравнивают строки, например, учитывает ли регистр и т. Д., И рефлектор быстро дал нам ответ. Это было полезно знать.

Если вы хотите выполнять сравнение строк с учетом регистра, вы можете использовать оператор switch, так как это быстрее, чем выполнение String.Compare в if / else. (Правка: читать « Что быстрее», включить строку или elseif для типа? Для некоторых реальных тестов производительности) Однако, если вы хотите сделать регистр нечувствительным, тогда лучше использовать if / else, так как полученный код не симпатичен.

switch (myString.ToLower())
{
  // not a good solution
}

Лучшее практическое правило - использовать операторы switch, если это имеет смысл (серьезно), например:

  • это улучшает читабельность вашего кода
  • вы сравниваете диапазон значений (float, int) или enum

Если вам нужно манипулировать значением для подачи в оператор switch (создать временную переменную для переключения), то вам, вероятно, следует использовать оператор управления if / else.

Обновление:

На самом деле лучше преобразовать строку в верхний регистр (например ToUpper()), поскольку, очевидно, существуют дополнительные оптимизации, которые компилятор точно в срок может делать по сравнению с ToLower(). Это микрооптимизация, но в узком кругу это может быть полезно.


Небольшое примечание:

Чтобы улучшить читаемость операторов switch, попробуйте следующее:

  • поставить наиболее вероятную ветвь первой, т.е.
  • если они все могут произойти, перечислите их в алфавитном порядке, чтобы их было легче найти.
  • никогда не используйте стандартную перехват для последнего оставшегося условия, это лениво и вызовет проблемы позже в жизни кода.
  • используйте стандартное универсальное средство для утверждения неизвестного условия, даже если это вряд ли когда-либо произойдет. это то, что утверждает, хорошо.
Деннис
источник
Во многих случаях использование ToLower () является правильным решением, особенно если существует много случаев и генерируется хеш-таблица.
Blaisorblade
«Если вам нужно манипулировать значением для подачи в оператор switch (создать временную переменную для переключения), вам, вероятно, следует использовать оператор управления if / else». - Хороший совет, спасибо.
Подлость
2

Оператор switch определенно быстрее, чем if if if. Есть скоростные испытания, которые были предоставлены на нем BlackWasp

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

- Проверьте это

Но сильно зависит от возможностей, которые вы пытаетесь объяснить, но я стараюсь использовать оператор switch всякий раз, когда это возможно.

Sleath
источник
1

Я думаю, не только C #, но и все языки на основе C: поскольку переключение ограничено константами, можно генерировать очень эффективный код, используя «таблицу переходов». Случай C на самом деле является старым добрым вычисляемым GOTO на FORTRAN, но случай C # все еще проверяет константу.

Это не тот случай, когда оптимизатор сможет сделать тот же код. Рассмотрим, например,

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}

Поскольку это составные логические значения, сгенерированный код должен вычислять значение и короткую замыкание. Теперь рассмотрим эквивалент

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}

Это может быть скомпилировано в

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x

потому что вы неявно говорите компилятору, что ему не нужно вычислять OR и тесты на равенство.

Чарли Мартин
источник
Нет никакой причины, по которой хороший оптимизатор не может обрабатывать первый код, если он реализован. «Компилятор не может оптимизировать» зависит только от семантических различий, которые может согласовать только человек (т. Е. Если вызывается f (), он не знает, что f () всегда возвращает 0 или 1).
Blaisorblade
0

Мой преподаватель кафедры предложил вам не менять формулировки, поскольку люди часто забывают перерыв или используют его неправильно. Я не могу вспомнить точно, что он сказал, но что-то похожее на то, что взгляд на некоторую основную кодовую базу, которая показывала примеры оператора switch (много лет назад), также содержал массу ошибок.


источник
Не совсем проблема в C #. Смотрите: stackoverflow.com/questions/174155/… ... а также прочитайте stackoverflow.com/questions/188461/… для обсуждения того, почему жизнь в страхе не может быть лучшей политикой ...
Shog9
0

Что-то, что я только что заметил, это то, что вы можете комбинировать операторы if / else и switch! Очень полезно, когда нужно проверить предварительные условия.

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}
Даже Миен
источник
3
Я знаю, что это старо, но я думаю, что технически вы ничего не «комбинируете». Каждый раз, когда у вас есть «else» без фигурных скобок, будет выполнен следующий оператор. Этим оператором может быть однострочный оператор, обычно показанный с отступом на следующей строке, или составные операторы, как в случае с if, switch, using, lock и т. Д. Другими словами, вы можете иметь «else if», « еще переключение "," еще использование "и т. д. Сказав это, мне нравится, как это выглядит и почти выглядит намеренно. (Отказ от ответственности: я не пробовал все это, поэтому я МОГУ ошибаться!)
Нельсон Ротермел
Нельсон, ты на 100% прав. Я понял, почему это происходит после того, как я опубликовал этот ответ.
Даже Mien
0

Я думаю, что переключение происходит быстрее, чем если условия вроде того, как посмотреть, есть ли такая программа:

Напишите программу для ввода любого числа (от 1 до 99) и проверьте, в каком слоте a) 1 - 9, затем слот один b) 11 - 19, затем слот два c) 21-29, затем слот три и так далее до 89- 99

Затем включите, если вам нужно выполнить много условий, но вы должны просто набрать текст.

Переключатель (нет / 10)

и в случае 0 = 1-9, в случае 1 = 11-19 и так далее

это будет так легко

Таких примеров еще много!

Сообщество
источник
0

оператор switch в основном представляет собой сравнение на равенство. События клавиатуры имеют большое преимущество по сравнению с оператором switch, когда в нем легко писать и читать код, тогда как в случае оператора if else отсутствие скобки {} также может вызывать беспокойство.

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}

Оператор if elseif подходит для более чем одного решения, если (theAmountOfApples больше 5 &&; themountOfApples меньше 10) сохраняют ваши яблоки, иначе, если (theAmountOfApples больше 10 || theAmountOfApples == 100) продают ваши яблоки. Я не пишу на C # или C ++, но я изучил его до того, как выучил Java, и это близкие языки.

Geen
источник
0

Одним из возможных недостатков операторов switch является отсутствие нескольких условий. Вы можете иметь несколько условий для операторов if (else), но не несколько случаев с разными условиями в переключателе.

Операторы switch не подходят для логических операций, выходящих за рамки простых булевых уравнений / выражений. Для этих булевых уравнений / выражений это в высшей степени подходит, но не для других логических операций.

У вас гораздо больше свободы с логикой, доступной в операторах If, но читаемость может пострадать, если инструкция If становится громоздкой или плохо обрабатывается.

У обоих есть место в зависимости от контекста того, с чем вы столкнулись.

Нил Мейер
источник