Кілька випадків у операторі комутатора


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
так, Чарльз, перший спосіб прекрасно працює, я його використовував у численних місцях. Це брудніше, ніж я хотів би, але це корисно. Я просто використав ці цілі числа як приклад. Реальні дані були різноманітнішими. Якщо if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} теж працював би, але це важче читати.
theo

5
чому ви вважаєте останнє бруднішим за перше. Останнє додає ще одне значення ,та те, яке не поділяється жодною іншою мовою c-стилю. Це здавалося б мені набагато бруднішим.
Джон Ханна

1
Можливо, ти взяв синтаксис 2-го з 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 краще для різних випадок справ ... за винятком того, що вони не пропускаються, як C # робить. Взяти трохи, дати трохи.
Brain2000

Я вважаю, що це вже не правильно. Дивіться stackoverflow.com/questions/20147879/… . Також на це саме запитання є відповідь stackoverflow.com/a/44848705/1073157
Dan Rayson

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 ... Заява справи :

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.
nickf

10
Тут ми маємо один з небагатьох дисплеїв, де візуальний базовий не такий потворний, а більш універсальний, ніж c #. Це цінний приклад!
bgmCoder

Це досить пристойно. Цікаво, чому це не додано із C # 8.0. Було б дуже приємно.
Sigex

69

У C # 7 (доступно за замовчуванням у Visual Studio 2017 / .NET Framework 4.6.2) тепер можливе перемикання на основі діапазону із заявою перемикача і допоможе вирішити проблему ОП.

Приклад:

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? Де карта Енума до інту?
Sigex

32

Ви можете залишити новий рядок, який дає вам:

case 1: case 2: case 3:
   break;

але я вважаю це поганим стилем.


20

.NET Framework 3.5 має діапазони:

Численні.Ранг від 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 не існує простого часткового додатка, яке б інкапсулювало факт, що параметр регістру той самий, але ви створюєте маленький допоміжний метод (трохи багатослівний, тхо).

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

Ось приклад того, як нова функціональна імпортна заява IMHO є більш потужною та елегантною, ніж стара імперативна.


3
Гарний вибір. Хоча одне, що слід зазначити - Численні. Ранг має аргументи 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) {DoAgetherThing ();}
David Hollowell - 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;
...
}

Це означатиме, що ви виділяєте масиви з кожним оператором переключення, правда? Чи не було б краще, якби ми мали їх як постійні змінні?
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 усі виконують одну і ту ж логіку, тоді оберніть цю логіку у звичайній формі і зателефонуйте її для кожного випадку. Я знаю, що це насправді не позбавляється від заяв справи, але воно реалізує хороший стиль і зводить обслуговування до мінімуму .....

[Редагувати] Додано альтернативну реалізацію, щоб відповідати початковому питанню ... [/ Редагувати]

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 тепер у нас є відповідність шаблонів, щоб ви могли зробити щось на кшталт:

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 (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практично можна уникнути (хоча я не вважаю це "жахливим" - це заповнення певної, структурованої ролі). У вашому прикладі, оскільки ви обернули органи справи у функції (гарна річ), випадок 0 може стати CaseZero(); CaseZeroOrOne(); break;. Не gotoпотрібно.
ToolmakerSteve

Посилання наполовину розірвано (переадресація, "Технічна документація на пенсію у Visual Studio 2003" ).
Пітер Мортенсен

2

Начебто було зроблено дуже багато роботи, щоб знайти способи отримати один із найменш використаних синтаксисів C #, щоб якось виглядати краще чи працювати краще. Особисто я вважаю, що оператор перемикання рідко варто використовувати. Я настійно пропоную проаналізувати, які саме дані ви протестуєте та які кінцеві результати бажаєте.

Скажімо, наприклад, що ви хочете швидко перевірити значення у відомому діапазоні, щоб побачити, чи є вони простими числами. Ви хочете, щоб ваш код не марнував обчислення, і ви можете знайти список простих чисел у діапазоні, який ви хочете в Інтернеті. Ви можете використовувати масовий оператор перемикання, щоб порівняти кожне значення з відомими простими числами.

Або ви можете просто створити карту масиву простих чисел та отримати негайні результати:

    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();
        }
    }

Можливо, ви хочете побачити, чи символ у рядку є шістнадцятковим. Ви можете використовувати нечесні та дещо великі оператори перемикача.

Або ви можете використовувати або регулярні вирази для тестування char, або скористатися функцією 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), тоді використовуйте enum для визначення дій та створення карти масиву переліків. Значення буде використано для індексації на карті масиву та отримання суми потрібної дії. Потім використовуйте або невеликий набір операторів IF, або дуже простий оператор переключення для обробки отриманого значення перерахунку.

Також приємна річ у масі масиву, який перетворює діапазон значень у дії, - це те, що її можна легко змінити за кодом. З жорстким провідним кодом ви не можете легко змінити поведінку під час виконання, але з картою масиву це легко.


Ви також можете зробити карту лямбда-виразу або делегата
Конрад Фрікс

Хороші бали. Один незначний коментар: Мені зазвичай легше підтримувати список значень, які відповідають даному випадку, ніж карта масиву. Проблема з картою масиву полягає в тому, що легко помилитися. Наприклад, замість масиву масивів праймес true / false, просто встановіть список простих чисел та завантажте їх у 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 (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;
 }

Ви також можете перевірити діапазон у корпусі перемикача C #: Корпус перемикача: чи можу я використовувати діапазон замість одного числа або якщо ви хочете зрозуміти основи випадку перемикача 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 # все ще є корінням; вони не є чистими мовами OO (слава Богу)). Процедурне програмування має чітко визначений потік логіки, визначений мовними конструкціями та методами, що викликають конвенції (як стек виконання часу зростає та скорочується). Оператор goto обходить цей потік, дозволяючи в основному довільно стрибати навколо.
Саміс

1
Я не кажу, що це гарний стиль, переконайтесь, але це робить те, про що було задано оригінальне питання.
scone

2
Ні, це не "робить те, про що було задано первісне питання". У початковому запитанні був код, який працював як є . Їм це не потрібно було виправити. І навіть якщо вони це зробили, це жахлива пропозиція. Його менш стисло і використовує goto. Гірше, що це абсолютно непотрібне використання goto, оскільки спрацьовує оригінальний синтаксис, заявлений ОП. Питання полягало в тому, чи існував більш стислий спосіб подати альтернативні випадки. Як люди відповіли за роки до вас , так - якщо ви готові поставити кілька справ у один рядок case 1: case 2:і якщо дозволяє редактор авто-стилів.
ToolmakerSteve

Єдина причина, по якій Goto вважається поганою, полягає в тому, що деяким людям важко дотримуватися логічного потоку. .Net MSIL (зібраний код об'єкта) використовує goto у всьому, тому що це швидко, але якщо .Net-код можна записати і бути таким же виконавцем без них, то краще не використовувати їх, і тому ви не заграєте людьми, як-от @ Похвальна відповідь ToolmakerSteve.
динамічнонк

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