Несколько случаев в выражении switch


582

Есть ли способ пролистать несколько операторов без case value:повторения?

Я знаю, что это работает:

switch (value)
{
   case 1:
   case 2:
   case 3:
      // Do some stuff
      break;
   case 4:
   case 5:
   case 6:
      // Do some different stuff
      break;
   default:
       // Default stuff
      break;
}

но я хотел бы сделать что-то вроде этого:

switch (value)
{
   case 1,2,3:
      // Do something
      break;
   case 4,5,6:
      // Do something
      break;
   default:
      // Do the Default
      break;
}

Это синтаксис, о котором я думаю из другого языка, или я что-то упустил?


Есть ли причина, по которой вы не просто используете оператор IF (если вы проверяете диапазон значений)?
Эрик Шуновер

2
да, Чарльз, первый способ работает хорошо, я использовал его во многих местах. Это грязнее, чем хотелось бы, но полезно. Я просто использовал эти целые числа в качестве примера. Реальные данные были более разнообразными. Если бы (1 || 2 || 3) {...} иначе if (4 || 5 || 6) {...} тоже сработало бы, но его сложнее читать.
Тео

5
почему вы считаете последнее более грязным, чем первое. Последний добавляет еще одно значение ,и тот, который не используется ни в каком другом языке c-style. Это показалось бы мне намного грязнее.
Джон Ханна

1
Возможно, вы взяли второй синтаксис от Ruby. Вот как это работает на этом языке (хотя переключение становится регистром, а регистр становится, когда, между прочим.)
juanpaco

4
Важное примечание . Диапазоны поддерживаются в случае коммутатора, начиная с C # v7 - см. Ответ
RBT

Ответы:


313

В C ++ и C # нет синтаксиса для второго метода, который вы упомянули.

Нет ничего плохого в вашем первом методе. Однако, если у вас очень большие диапазоны, просто используйте серию операторов if.


5
В качестве дополнения я хотел добавить ссылку на спецификацию языка C #, доступную в MSDN по адресу msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Ричард МакГуайр,

Пользователь может использовать некоторые if (или поиск в таблице), чтобы уменьшить ввод до набора перечислений и включить перечисление.
Харви

5
вероятно выбрал это из VB.net
Джордж Бирбилис

1
VB.net лучше подходит для различных операторов case, за исключением того, что они не пропускаются, как в C #. Возьми немного, дай немного.
Brain2000

Я считаю, что это больше не правильно. См. Stackoverflow.com/questions/20147879/… . Также на этот самый вопрос есть ответ stackoverflow.com/a/44848705/1073157
Дэн Рэйсон

700

Я думаю, на это уже ответили. Тем не менее, я думаю, что вы все еще можете смешать оба варианта синтаксически лучше, выполнив:

switch (value)
{
    case 1: case 2: case 3:          
        // Do Something
        break;
    case 4: case 5: case 6: 
        // Do Something
        break;
    default:
        // Do Something
        break;
}

3
'переключатель' должен быть в нижнем регистре для C #?
Остин Харрис

3
Свернутый код удлиняется до первого примера в вопросе. Можно просто сделать это так, как в вопросе.
MetalPhoenix

10
Зачем беспокоиться? Автоматический отступ в Visual Studio 2013 все равно вернет его в формат исходного вопроса.
Густав

14
@T_D получает поддержку, потому что на самом деле отвечает на вопрос. ОП сказал, что я что-то упустил ... Карлос ответил тем, чего ему не хватает. Кажется, довольно порезанный и высушенный для меня. Не ненавидь, что у него 422 голоса.
Майк Девенни

8
@MikeDevenney Тогда вы по-разному истолковали вопрос, насколько я вижу правильный ответ: «Нет, у c # нет синтаксиса для этого». Если кто-то спросит: «Можно ли налить жидкость в стакан, который я держу вверх ногами?» ответ должен быть «нет», а не «вы можете наливать жидкость вверх, если вы смотрите на нее с ног на голову и используете свое воображение», потому что этот ответ все об использовании воображения. Если вы используете обычный синтаксис, но плохо его форматируете, он выглядит как другой синтаксис с некоторым воображением. Надеюсь, вы
поняли

74

Этот синтаксис взят из выражения Visual Basic Select ... Case :

Dim number As Integer = 8
Select Case number
    Case 1 To 5
        Debug.WriteLine("Between 1 and 5, inclusive")
        ' The following is the only Case clause that evaluates to True.
    Case 6, 7, 8
        Debug.WriteLine("Between 6 and 8, inclusive")
    Case Is < 1
        Debug.WriteLine("Equal to 9 or 10")
    Case Else
        Debug.WriteLine("Not between 1 and 10, inclusive")
End Select

Вы не можете использовать этот синтаксис в C #. Вместо этого вы должны использовать синтаксис из вашего первого примера.


49
это одна из немногих вещей, которые мне не хватает в * Basic.
Ник

10
Здесь у нас есть один из немногих дисплеев, где Visual Basic не так страшен и более универсален, чем C #. Это ценный пример!
bgmCoder

Это довольно прилично. Интересно, почему это не было добавлено в C # 8.0? Было бы довольно мило.
Sigex

69

В C # 7 (доступно по умолчанию в Visual Studio 2017 / .NET Framework 4.6.2) переключение на основе диапазона теперь возможно с помощью оператора switch и поможет решить проблему OP.

Пример:

int i = 5;

switch (i)
{
    case int n when (n >= 7):
        Console.WriteLine($"I am 7 or above: {n}");
        break;

    case int n when (n >= 4 && n <= 6 ):
        Console.WriteLine($"I am between 4 and 6: {n}");
        break;

    case int n when (n <= 3):
        Console.WriteLine($"I am 3 or less: {n}");
        break;
}

// Output: I am between 4 and 6: 5

Ноты:

  • Скобки (и )не обязательны в whenусловии, но используются в этом примере, чтобы выделить сравнение (я).
  • varможет также использоваться вместо int. Например: case var n when n >= 7:.

3
Это (сопоставление с образцом), как правило, должно быть наилучшей практикой, когда вы можете использовать C # 7.x или выше, так как это намного яснее, чем другие ответы.
UndyingJellyfish

Есть ли способ достичь этого с помощью списка Enums? Где Enums отображаются на int?
Sigex

33

Вы можете пропустить новую строку, которая дает вам:

case 1: case 2: case 3:
   break;

но я считаю этот плохой стиль.


20

В .NET Framework 3.5 есть диапазоны:

Enumerable.Range от MSDN

Вы можете использовать его с «Содержит» и оператором IF, так как, как кто-то сказал, оператор SWITCH использует оператор «==».

Вот пример:

int c = 2;
if(Enumerable.Range(0,10).Contains(c))
    DoThing();
else if(Enumerable.Range(11,20).Contains(c))
    DoAnotherThing();

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

public static void MySwitchWithEnumerable(int switchcase, int startNumber, int endNumber, Action action)
{
    if(Enumerable.Range(startNumber, endNumber).Contains(switchcase))
        action();
}

Старый пример с этим новым методом:

MySwitchWithEnumerable(c, 0, 10, DoThing);
MySwitchWithEnumerable(c, 10, 20, DoAnotherThing);

Поскольку вы передаете действия, а не значения, вы должны опустить скобки, это очень важно. Если вам нужна функция с аргументами, просто измените тип Actionна Action<ParameterType>. Если вам нужны возвращаемые значения, используйте Func<ParameterType, ReturnType>.

В C # 3.0 нет простого приложения для частичного применения, чтобы инкапсулировать тот факт, что параметр case такой же, но вы создали небольшой вспомогательный метод (немного многословно, хотя).

public static void MySwitchWithEnumerable(int startNumber, int endNumber, Action action){ 
    MySwitchWithEnumerable(3, startNumber, endNumber, action); 
}

Вот пример того, как новые функциональные импортированные операторы ИМХО более мощные и элегантные, чем старые императивные.


3
Хороший выбор. Однако следует отметить одну вещь - Enumerable.Range имеет аргументы int startи int count. Ваши примеры не будут работать так, как они были написаны. Вы пишете это, как будто второй аргумент int end. Например - Enumerable.Range(11,20)приведет к 20 числам, начинающимся с 11, а не к числам от 11 до 20.
Габриэль МакАдамс

хотя, если работаешь с Enum, то почему не так? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();}
Дэвид Холлоуэлл - MSFT,

4
Обратите внимание , что Enumerable.Range(11,20).Contains(c)эквивалентно for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;Если у вас есть большой выбор , что это займет много времени, а только с помощью >и <будет быстрыми и постоянная время.
Джон Ханна

Улучшение: MySwitchWithEnumerableвозвращение void- слабый дизайн для этой ситуации. ПРИЧИНА: Вы преобразовали if-elseв серию независимых операторов - которые скрывают намерение, а именно, что они являются взаимоисключающими, action- выполняется только одно . Вместо того, чтобы вернуться bool, с телом if (..) { action(); return true; } else return false;Вызывающий сайт затем показывает намерение: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. Это предпочтительнее. Тем не менее, это также больше не является значительным улучшением по сравнению с вашей первоначальной версией, для этого простого случая.
ToolmakerSteve

15

Вот полное решение C # 7 ...

switch (value)
{
   case var s when new[] { 1,2,3 }.Contains(s):
      // Do something
      break;
   case var s when new[] { 4,5,6 }.Contains(s):
      // Do something
      break;
   default:
      // Do the default
      break;
}

Это работает со строками тоже ...

switch (mystring)
{
   case var s when new[] { "Alpha","Beta","Gamma" }.Contains(s):
      // Do something
      break;
...
}

Это будет означать, что вы выделяете массивы для каждого оператора switch, верно? Разве не было бы лучше, если бы они были постоянными переменными?
MaLiN2223

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

11

Код ниже не будет работать:

case 1 | 3 | 5:
// Not working do something

Единственный способ сделать это:

case 1: case 2: case 3:
// Do something
break;

Код, который вы ищете, работает в Visual Basic, где вы легко можете поместить в диапазоны ... в качестве noneопции switchоператора или if elseудобных блоков, я бы посоветовал в очень крайнем случае создать .dll с Visual Basic и импортировать обратно в ваш проект C #.

Примечание: эквивалентный переключатель в Visual Basic есть Select Case.


7

Другим вариантом будет использование рутины. Если все случаи 1-3 выполняют одну и ту же логику, заключите эту логику в подпрограмму и вызовите ее для каждого случая. Я знаю, что на самом деле это не избавляет от операторов case, но реализует хороший стиль и сводит обслуживание к минимуму .....

[Edit] Добавлена ​​альтернативная реализация для соответствия оригинальному вопросу ... [/ Edit]

switch (x)
{
   case 1:
      DoSomething();
      break;
   case 2:
      DoSomething();
      break;
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

Alt

switch (x)
{
   case 1:
   case 2:
   case 3:
      DoSomething();
      break;
   ...
}

private void DoSomething()
{
   ...
}

5

Одним из менее известных аспектов переключения в C # является то, что он опирается на оператор =, и, поскольку он может быть переопределен, вы можете получить что-то вроде этого:


string s = foo();

switch (s) {
  case "abc": /*...*/ break;
  case "def": /*...*/ break;
}

4
позже это может стать большой ошибкой для кого-то другого, пытающегося прочитать код
Эндрю Гарри

5

gcc реализует расширение языка C для поддержки последовательных диапазонов:

switch (value)
{
   case 1...3:
      //Do Something
      break;
   case 4...6:
      //Do Something
      break;
   default:
      //Do the Default
      break;
}

Редактировать : только что заметил тег C # в вопросе, так что, вероятно, gcc ответ не поможет.


4

В C # 7 у нас теперь есть Pattern Matching, поэтому вы можете сделать что-то вроде:

switch (age)
{
  case 50:
    ageBlock = "the big five-oh";
    break;
  case var testAge when (new List<int>()
      { 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 }).Contains(testAge):
    ageBlock = "octogenarian";
    break;
  case var testAge when ((testAge >= 90) & (testAge <= 99)):
    ageBlock = "nonagenarian";
    break;
  case var testAge when (testAge >= 100):
    ageBlock = "centenarian";
    break;
  default:
    ageBlock = "just old";
    break;
}

3

На самом деле мне тоже не нравится команда GOTO, но она есть в официальных материалах Microsoft, и здесь все допустимые синтаксисы.

Если конечная точка списка операторов секции switch достижима, возникает ошибка времени компиляции. Это известно как правило «не провалиться». Пример

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
default:
   CaseOthers();
   break;
}

допустимо, потому что ни одна секция коммутатора не имеет достижимой конечной точки. В отличие от C и C ++, выполнение раздела переключателя не может "провалиться" до следующего раздела переключателя, и пример

switch (i) {
case 0:
   CaseZero();
case 1:
   CaseZeroOrOne();
default:
   CaseAny();
}

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

switch (i) {
case 0:
   CaseZero();
   goto case 1;
case 1:
   CaseZeroOrOne();
   goto default;
default:
   CaseAny();
   break;
}

Допускается использование нескольких меток в секции переключателей. Пример

switch (i) {
case 0:
   CaseZero();
   break;
case 1:
   CaseOne();
   break;
case 2:
default:
   CaseTwo();
   break;
}

Я верю, что в данном конкретном случае GOTO можно использовать, и на самом деле это единственный путь к успеху.

Источник: http://msdn.microsoft.com/en-us/library/aa664749%28v=vs.71%29.aspx


Обратите внимание, что на практике этого gotoпочти всегда можно избежать (хотя я не считаю его здесь «ужасным» - он выполняет определенную, структурированную роль). В вашем примере, поскольку вы обернули тела case в функции (что хорошо), case 0 может стать CaseZero(); CaseZeroOrOne(); break;. Не gotoтребуется
ToolmakerSteve

Ссылка наполовину неработающая (перенаправляет «Visual Studio 2003 Удаленная техническая документация» ).
Питер Мортенсен

2

Кажется, что была проделана огромная работа по поиску способов получить один из наименее используемых синтаксисов C #, чтобы как-то выглядеть лучше или работать лучше. Лично я считаю, что оператор switch редко стоит использовать. Я настоятельно рекомендую проанализировать, какие данные вы тестируете, и какие конечные результаты вы хотите получить.

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

Или вы можете просто создать массив карт простых чисел и получить немедленные результаты:

    bool[] Primes = new bool[] {
        false, false, true, true, false, true, false,    
        true, false, false, false, true, false, true,
        false,false,false,true,false,true,false};
    private void button1_Click(object sender, EventArgs e) {
        int Value = Convert.ToInt32(textBox1.Text);
        if ((Value >= 0) && (Value < Primes.Length)) {
            bool IsPrime = Primes[Value];
            textBox2.Text = IsPrime.ToString();
        }
    }

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

Или вы можете использовать либо регулярные выражения для проверки символа, либо использовать функцию IndexOf для поиска символа в строке известных шестнадцатеричных букв:

        private void textBox2_TextChanged(object sender, EventArgs e) {
        try {
            textBox1.Text = ("0123456789ABCDEFGabcdefg".IndexOf(textBox2.Text[0]) >= 0).ToString();
        } catch {
        }
    }

Допустим, вы хотите выполнить одно из 3 различных действий в зависимости от значения в диапазоне от 1 до 24. Я бы предложил использовать набор операторов IF. И если это стало слишком сложным (или числа были больше, например, 5 различных действий в зависимости от значения в диапазоне от 1 до 90), используйте перечисление для определения действий и создания карты массивов перечислений. Затем значение будет использоваться для индексации в карте массива и получения перечисления нужного вам действия. Затем используйте небольшой набор операторов IF или очень простой оператор switch для обработки полученного значения перечисления.

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


Вы также можете сопоставить с лямбда-выражением или делегатом
Конрад Фрикс

Хорошие моменты. Небольшой комментарий: мне обычно легче вести список значений, соответствующих данному случаю, чем карту массива. Проблема с картой массива в том, что легко ошибиться. Например, вместо карты массивов простых чисел true / falses просто создайте список простых чисел и загрузите их в HashSet для производительности поиска. Даже если существует более двух случаев, обычно все, кроме одного, представляют собой небольшой список, поэтому создайте либо HashSet перечислений (если они редкие), либо карту массива в коде из списков других случаев.
ToolmakerSteve

1

Чтобы добавить к разговору, используя .NET 4.6.2, я также смог сделать следующее. Я проверил код, и он работал на меня.

Вы также можете сделать несколько операторов «ИЛИ», как показано ниже:

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when b.Contains("text3") || b.Contains("text4") || b.Contains("text5"):
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Вы также можете проверить, соответствует ли оно значению в массиве:

            string[] statuses = { "text3", "text4", "text5"};

            switch (value)
            {
                case string a when a.Contains("text1"):
                    // Do Something
                    break;
                case string b when statuses.Contains(value):                        
                    // Do Something else
                    break;
                default:
                    // Or do this by default
                    break;
            }

Разве это не зависит от версии C #, а не от версии .NET?
Питер Мортенсен

1

Если у вас очень много строк (или любого другого типа), делающих одно и то же, я рекомендую использовать список строк в сочетании со свойством string.Contains.

Так что если у вас есть большой оператор switch, например:

switch (stringValue)
{
    case "cat":
    case "dog":
    case "string3":
    ...
    case "+1000 more string": // Too many string to write a case for all!
        // Do something;
    case "a lonely case"
        // Do something else;
    .
    .
    .
}

Вы могли бы хотеть заменить это ifутверждением как это:

// Define all the similar "case" string in a List
List<string> listString = new List<string>(){ "cat", "dog", "string3", "+1000 more string"};
// Use string.Contains to find what you are looking for
if (listString.Contains(stringValue))
{
    // Do something;
}
else
{
    // Then go back to a switch statement inside the else for the remaining cases if you really need to
}

Этот масштаб хорошо подходит для любого числа строковых падежей.


0

Я думаю, что этот лучше в C # 7 или выше.

switch (value)
{
    case var s when new[] { 1,2 }.Contains(s):
    // Do something
     break;

    default:
    // Do the default
    break;
 }

Вы также можете проверить Range в регистре переключателя C #: Switch case: могу ли я использовать диапазон вместо одного числа? Или если вы хотите понять основы переключения C #


-5

Для этого вы должны использовать оператор goto. Такие как:

    switch(value){
    case 1:
        goto case 3;
    case 2:
        goto case 3;
    case 3:
        DoCase123();
    //This would work too, but I'm not sure if it's slower
    case 4:
        goto case 5;
    case 5:
        goto case 6;
    case 6:
        goto case 7;
    case 7:
        DoCase4567();
    }

7
@scone goto нарушает фундаментальный принцип процедурного программирования (в котором все еще внедрены c ++ и c #; это не чистые ОО-языки (слава Богу)). Процедурное программирование имеет четко определенный поток логики, определяемый языковыми конструкциями и соглашениями о вызовах методов (как растет и уменьшается стек времени выполнения). Оператор goto обходит этот поток, позволяя, в основном, произвольно прыгать.
Самис

1
Я не говорю, что это хороший стиль, конечно, но он делает то, о чем просил оригинальный вопрос.
Скон

2
Нет, это не «делает то, о чем просил оригинальный вопрос». У исходного вопроса был код, который работал как есть . Им не нужно это исправить. И даже если они это сделали, это ужасное предложение. Это менее сжато, и использует goto. Хуже того, это совершенно ненужное использование goto, так как оригинальный синтаксис, указанный OP, работает. Вопрос заключался в том, существует ли более краткий способ дать альтернативные случаи. Как люди ответили за годы до того, как вы ответили , да, есть, если вы хотите поместить несколько дел в одну строку case 1: case 2:, и если позволяет автоматический стиль редактора.
ToolmakerSteve

Единственная причина, по которой goto's определяется как плохая, заключается в том, что некоторым людям трудно следовать логике. .Net MSIL (собранный объектный код) использует goto повсеместно, потому что это быстро, но если .Net-код может быть написан и быть таким же быстрым без них, то лучше его не использовать, и поэтому вы не будете огорчены людьми вроде @ ToolmakerSteve - снисходительный ответ.
dynamiclynk

@wchoward - Пожалуйста, прочитайте мой ответ более внимательно. Моя жалоба касается не только использования goto . Я возразил, потому что вопрос показал код, который уже работает как есть , и этот ответ а) берет этот рабочий код и делает его более подробным и менее структурированным, без пользы , б) не отвечает на вопрос.
ToolmakerSteve
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.