Plusieurs cas dans l'instruction switch


582

Existe-t-il un moyen de passer à travers plusieurs déclarations de cas sans préciser case value: répéter à plusieurs reprises?

Je sais que cela fonctionne:

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

mais je voudrais faire quelque chose comme ça:

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

Cette syntaxe à laquelle je pense provient-elle d'un autre langage ou manque-t-il quelque chose?


Y a-t-il une raison pour laquelle vous n'utilisez pas simplement une instruction IF (si vous vérifiez une plage d'ints)?
Eric Schoonover

2
oui charlse, la première façon fonctionne bien, je l'ai utilisé dans de nombreux endroits. C'est plus sale que je ne le voudrais, mais c'est utile. Je viens d'utiliser ces entiers comme exemple. Les données réelles étaient plus variées. Un if (1 || 2 || 3) {...} else if (4 || 5 || 6) {...} aurait aussi fonctionné, mais c'est plus difficile à lire.
theo

5
pourquoi considérez-vous ce dernier plus sale que le premier. Ce dernier ajoute encore une autre signification à ,une autre qui n'est partagée avec aucun autre langage de style C. Cela me semblerait beaucoup plus sale.
Jon Hanna

1
Vous avez peut-être récupéré la syntaxe du 2e sur Ruby. Voilà comment cela fonctionne dans cette langue (bien que le changement devienne cas, et le cas devient quand, entre autres choses.)
juanpaco

4
Remarque importante . Les plages sont prises en charge dans le cas de commutateur à partir de C # v7 - Veuillez consulter la réponse de
RBT

Réponses:


313

Il n'y a pas de syntaxe en C ++ ni en C # pour la deuxième méthode que vous avez mentionnée.

Il n'y a rien de mal avec votre première méthode. Si toutefois vous avez de très grandes plages, utilisez simplement une série d'instructions if.


5
En complément, je voulais ajouter un lien vers la spécification du langage C # disponible sur MSDN à msdn.microsoft.com/en-us/vcsharp/aa336809.aspx
Richard McGuire

L'utilisateur peut utiliser des if (ou une recherche de table) pour réduire l'entrée à un ensemble d'énumérations et activer l'énumération.
Harvey

5
probablement choisi cela sur VB.net
George Birbilis

1
VB.net est meilleur pour diverses déclarations de cas ... sauf qu'elles ne passent pas comme C # le fait. Prenez un peu, donnez un peu.
Brain2000

Je pense que ce n'est plus correct. Voir stackoverflow.com/questions/20147879/… . Sur cette même question, il y a aussi une réponse stackoverflow.com/a/44848705/1073157
Dan Rayson

700

Je suppose que cela a déjà été répondu. Cependant, je pense que vous pouvez toujours mélanger les deux options d'une manière syntaxiquement meilleure en faisant:

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
'switch' devrait être en minuscules pour c #?
Austin Harris

3
Le code réduit est allongé jusqu'au premier exemple de la question. Autant le faire comme il est dans la question.
MetalPhoenix

10
Pourquoi s'embêter? Le pénétrateur automatique dans Visual Studio 2013 rétablira tout de même le format dans la question d'origine.
Gustav

14
@T_D, il reçoit du soutien, car il répond en fait à la question. L'OP a dit: «Est-ce que je manque quelque chose ... Carlos a répondu avec ce qu'il manquait. Semble assez coupé et séché pour moi. Ne déteste pas qu'il a 422 votes positifs.
Mike Devenney

8
@MikeDevenney Ensuite, vous avez interprété la question différemment, pour autant que je vois la bonne réponse serait "non, c # n'a pas de syntaxe pour cela". Si quelqu'un demande "est-il possible de verser du liquide dans un verre que je tiens à l'envers?" la réponse doit être "non" et non "vous pouvez verser du liquide si vous le regardez à l'envers et utilisez votre imagination", car cette réponse est une question d'imagination. Si vous utilisez la syntaxe régulière mais que vous la formatez mal, elle ressemble à une autre syntaxe, avec un peu d'imagination. J'espère que vous comprendrez mon point ...: P
T_D

74

Cette syntaxe provient de l' instruction 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

Vous ne pouvez pas utiliser cette syntaxe en C #. Au lieu de cela, vous devez utiliser la syntaxe de votre premier exemple.


49
c'est l'une des rares choses qui me manquent * Basic.
nickf

10
Ici, nous avons l'un des rares écrans où Visual Basic n'est pas aussi moche et plus polyvalent que C #. Ceci est un exemple précieux!
bgmCoder

C'est assez décent. Je me demande pourquoi cela n'a pas été ajouté avec C # 8.0. Ce serait plutôt sympa.
Sigex

69

En C # 7 (disponible par défaut dans Visual Studio 2017 / .NET Framework 4.6.2), la commutation basée sur la plage est désormais possible avec l' instruction switch et aiderait à résoudre le problème de l'OP.

Exemple:

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

Remarques:

  • Les parenthèses (et )ne sont pas obligatoires dans la whencondition, mais sont utilisées dans cet exemple pour mettre en évidence la ou les comparaisons.
  • varpeut également être utilisé à la place de int. Par exemple: case var n when n >= 7:.

3
Cela (correspondance de motifs) devrait généralement être la meilleure pratique lorsque vous pouvez utiliser C # 7.x ou supérieur, car il est beaucoup plus clair que les autres réponses.
UndyingJellyfish

Existe-t-il un moyen d'y parvenir avec une liste d'énumérations? Où les Enums mappent-ils sur int?
Sigex

33

Vous pouvez laisser de côté la nouvelle ligne qui vous donne:

case 1: case 2: case 3:
   break;

mais je considère ce mauvais style.


20

.NET Framework 3.5 a des plages:

Enumerable.Range de MSDN

vous pouvez l'utiliser avec "contains" et l'instruction IF, car comme quelqu'un l'a dit, l'instruction SWITCH utilise l'opérateur "==".

Voici un exemple:

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

Mais je pense que nous pouvons avoir plus de plaisir: puisque vous n'aurez pas besoin des valeurs de retour et que cette action ne prend pas de paramètres, vous pouvez facilement utiliser des actions!

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

Le vieil exemple avec cette nouvelle méthode:

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

Puisque vous passez des actions, pas des valeurs, vous devez omettre les parenthèses, c'est très important. Si vous avez besoin d'une fonction avec des arguments, changez simplement le type de Actionen Action<ParameterType>. Si vous avez besoin de valeurs de retour, utilisez Func<ParameterType, ReturnType>.

En C # 3.0, il n'y a pas d' application partielle facile pour encapsuler le fait que le paramètre case est le même, mais vous créez une petite méthode d'aide (un peu verbeuse, mais).

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

Voici un exemple de la façon dont la nouvelle déclaration fonctionnelle importée est à mon humble avis plus puissante et élégante que l'ancienne impérative.


3
Bon choix. Une chose à noter, cependant - Enumerable.Range a des arguments int startet int count. Vos exemples ne fonctionneront pas exactement comme ils ont été écrits. Vous l'écrivez comme si le deuxième argument était int end. Par exemple - Enumerable.Range(11,20)entraînerait 20 nombres commençant par 11, et non des nombres de 11 à 20.
Gabriel McAdams

bien que, si vous travaillez avec un Enum, pourquoi pas quelque chose comme ça? if (Enumerable.Range (MyEnum.A, MyEnum.M) {DoThing ();} else if (Enumerable.Range (MyEnum.N, MyEnum.Z) {DoAnotherThing ();}
David Hollowell - MSFT

4
Notez que cela Enumerable.Range(11,20).Contains(c)équivaut à for(int i = 11; i < 21; ++i){ if (i == c) return true; } return false;Si vous aviez une large plage, cela prendrait beaucoup de temps, tout en utilisant simplement >et <serait rapide et à temps constant.
Jon Hanna

Une amélioration: le MySwitchWithEnumerableretour voidest une conception faible pour cette situation. RAISON: Vous avez converti un if-elseen une série d'instructions indépendantes - qui cache l'intention, c'est-à-dire qu'elles s'excluent mutuellement - une seule actionest exécutée. Retour à la place bool, avec le corps if (..) { action(); return true; } else return false;Le site appelant montre alors l'intention: if (MySwitchWithEnumerable(..)) else (MySwitchWithEnumerable(..));. C'est préférable. Cependant, ce n'est également plus une amélioration significative par rapport à votre version d'origine, pour ce cas simple.
ToolmakerSteve

15

Voici la solution complète 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;
}

Cela fonctionne aussi avec les cordes ...

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

Cela signifie que vous allouez les tableaux à chaque instruction switch, non? Ne serait-il pas préférable de les avoir comme variables constantes?
MaLiN2223

Élégant, mais il serait en effet bon de savoir si le compilateur optimise ce scénario afin que les invocations répétées n'entraînent pas à chaque fois la surcharge de la construction du tableau; définir les tableaux à l'avance est une option, mais enlève beaucoup d'élégance.
mklement0

11

Le code ci - dessous ne fonctionnera pas :

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

La seule façon de procéder est:

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

Le code que vous recherchez fonctionne en Visual Basic où vous pouvez facilement mettre des plages ... dans l' noneoption de l' switchinstruction ouif else blocs pratiques, je suggère, à un point très extrême, de créer .dll avec Visual Basic et de l'importer à votre projet C #.

Remarque: l'équivalent de commutateur dans Visual Basic est Select Case.


7

Une autre option serait d'utiliser une routine. Si les cas 1 à 3 exécutent tous la même logique, encapsulez cette logique dans une routine et appelez-la pour chaque cas. Je sais que cela ne supprime pas réellement les déclarations de cas, mais cela met en œuvre un bon style et maintient la maintenance au minimum .....

[Modifier] Ajout d'une implémentation alternative pour correspondre à la question d'origine ... [/ Modifier]

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

Une facette moins connue du commutateur en C # est qu'il repose sur l' opérateur = et comme il peut être remplacé, vous pourriez avoir quelque chose comme ceci:


string s = foo();

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

4
cela pourrait devenir un gros problème plus tard pour quelqu'un d'autre essayant de lire le code
Andrew Harry

5

gcc implémente une extension du langage C pour prendre en charge les plages séquentielles:

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

Edit : Je viens de remarquer la balise C # sur la question, donc probablement une réponse gcc n'aide pas.


4

En C # 7, nous avons maintenant la correspondance de motifs afin que vous puissiez faire quelque chose comme:

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

En fait, je n'aime pas non plus la commande GOTO, mais elle est dans les documents officiels de Microsoft, et voici toutes les syntaxes autorisées.

Si le point final de la liste d'instructions d'une section de commutateur est accessible, une erreur de compilation se produit. C'est ce que l'on appelle la règle de «non-répercussion». L'exemple

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

est valide car aucune section de commutateur n'a de point de fin accessible. Contrairement à C et C ++, l'exécution d'une section de commutateur n'est pas autorisée à "passer" à la section de commutateur suivante, et l'exemple

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

entraîne une erreur de compilation. Lorsque l'exécution d'une section de commutateur doit être suivie par l'exécution d'une autre section de commutateur, un cas goto explicite ou une instruction par défaut goto doit être utilisé:

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

Plusieurs étiquettes sont autorisées dans une section de commutation. L'exemple

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

Je crois que dans ce cas particulier, le GOTO peut être utilisé, et c'est en fait le seul moyen de passer à travers.

Source: http://msdn.microsoft.com/en-us/library/aa664749%28v=vs.71%29.aspx


Notez qu'en pratique, cela gotopeut presque toujours être évité (bien que je ne le considère pas comme "terrible" ici - il remplit un rôle spécifique et structuré). Dans votre exemple, parce que vous avez encapsulé les corps de cas dans des fonctions (une bonne chose), le cas 0 peut devenir CaseZero(); CaseZeroOrOne(); break;. Non gotorequis.
ToolmakerSteve

Le lien est à moitié rompu (redirige, "Documentation technique retirée de Visual Studio 2003" ).
Peter Mortensen

2

Beaucoup de travail semble avoir été fait pour trouver des moyens d'obtenir une des syntaxes C # les moins utilisées pour mieux paraître ou mieux fonctionner. Personnellement, je trouve que l'instruction switch vaut rarement la peine d'être utilisée. Je suggère fortement d'analyser les données que vous testez et les résultats finaux que vous recherchez.

Disons par exemple que vous souhaitez tester rapidement des valeurs dans une plage connue pour voir s'il s'agit de nombres premiers. Vous voulez éviter que votre code fasse les calculs inutiles et vous pouvez trouver une liste de nombres premiers dans la plage que vous souhaitez en ligne. Vous pouvez utiliser une instruction switch massive pour comparer chaque valeur à des nombres premiers connus.

Ou vous pouvez simplement créer une carte matricielle de nombres premiers et obtenir des résultats immédiats:

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

Vous voulez peut-être voir si un caractère dans une chaîne est hexadécimal. Vous pouvez utiliser une instruction switch ungly et quelque peu volumineuse.

Ou vous pouvez utiliser des expressions régulières pour tester le caractère ou utiliser la fonction IndexOf pour rechercher le caractère dans une chaîne de lettres hexadécimales connues:

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

Supposons que vous souhaitiez effectuer l'une des 3 actions différentes en fonction d'une valeur comprise entre 1 et 24. Je suggère d'utiliser un ensemble d'instructions IF. Et si cela devenait trop complexe (ou si les nombres étaient plus grands, comme 5 actions différentes selon une valeur comprise entre 1 et 90), utilisez une énumération pour définir les actions et créer une carte de tableau des énumérations. La valeur serait ensuite utilisée pour indexer dans la carte du tableau et obtenir l'énumération de l'action souhaitée. Utilisez ensuite un petit ensemble d'instructions IF ou une instruction switch très simple pour traiter la valeur d'énumération résultante.

En outre, la bonne chose à propos d'une carte de tableau qui convertit une plage de valeurs en actions est qu'elle peut être facilement modifiée par le code. Avec du code câblé, vous ne pouvez pas facilement changer de comportement lors de l'exécution, mais avec une carte de tableau, c'est facile.


Vous pouvez également mapper vers l'expression lambda ou un délégué
Conrad Frix

Bons points. Un commentaire mineur: je trouve généralement plus facile de maintenir une liste des valeurs qui correspondent à un cas donné, qu'une carte de tableau. Le problème avec la carte de tableau est qu'il est facile de se tromper. Par exemple, au lieu de la carte du tableau primes de true / falses, ayez simplement une liste de nombres premiers et chargez-les dans un HashSet pour les performances de recherche. Même s'il y a plus de deux cas, généralement tous les cas sauf un sont une petite liste, alors créez un HashSet d'énumérations (si elles sont rares) ou une carte de tableau, en code, à partir des listes des autres cas.
ToolmakerSteve

1

Juste pour ajouter à la conversation, en utilisant .NET 4.6.2, j'ai également pu faire ce qui suit. J'ai testé le code et cela a fonctionné pour moi.

Vous pouvez également faire plusieurs instructions "OU", comme ci-dessous:

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

Vous pouvez également vérifier s'il correspond à une valeur dans un tableau:

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

Cela ne dépend-il pas de la version C #, pas de la version .NET?
Peter Mortensen

1

Si vous avez un très grand nombre de chaînes (ou tout autre type) faisant toutes la même chose, je recommande l'utilisation d'une liste de chaînes combinée avec la propriété string.Contains.

Donc, si vous avez une grosse instruction switch comme ceci:

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;
    .
    .
    .
}

Vous voudrez peut-être le remplacer par une ifdéclaration comme celle-ci:

// 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
}

Cette échelle convient bien à n'importe quel nombre de cas de chaîne.



-5

Pour cela, vous utiliseriez une instruction goto. Tel que:

    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 brise un principe fondamental de la programmation procédurale (dont c ++ et c # sont toujours enracinés; ce ne sont pas des langages OO purs (Dieu merci)). La programmation procédurale a un flux de logique bien défini déterminé par les constructions de langage et les conventions d'appel de méthode (comment la pile d'exécution augmente et se rétrécit). L'instruction goto contourne ce flux en permettant essentiellement des sauts arbitraires.
samis

1
Je ne dis pas que c'est un bon style, persévérez, mais ça fait ce que la question initiale demandait.
scone

2
Non, il ne "fait pas ce que la question initiale demandait". La question d'origine avait un code qui fonctionnait tel quel . Ils n'avaient pas besoin de le réparer. Et même s'ils l'ont fait, c'est une horrible suggestion. Son moins concis et utilise goto. Pire, c'est une utilisation complètement inutile de goto, car la syntaxe originale indiquée par OP fonctionne. La question était de savoir s'il y avait une manière plus concise de donner les cas alternatifs. Comme les gens ont répondu des années avant vous , oui, c'est le cas - si vous êtes prêt à mettre les plusieurs cas sur une seule ligne case 1: case 2:, et si le style automatique de l'éditeur le permet.
ToolmakerSteve

La seule raison pour laquelle les goto sont jugés mauvais est que certaines personnes ont du mal à suivre le flux logique. .Net MSIL (code objet assemblé) utilise goto partout car il est rapide, mais si le code .Net peut être écrit et être tout aussi performant sans eux, il vaut mieux ne pas les utiliser et ainsi vous ne serez pas flambé par des gens comme @ Réponse condescendante de ToolmakerSteve.
dynamiclynk

@wchoward - Veuillez lire ma réponse plus attentivement. Ma plainte ne concerne pas seulement l'utilisation de goto . Je me suis opposé parce que la question montrait du code qui fonctionne déjà tel quel, et cette réponse a) prend ce code de travail et le rend plus verbeux et moins bien structuré, sans aucun avantage , b) ne répond pas à la question.
ToolmakerSteve
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.