75
votes

Expression de commutateur C # 8 avec plusieurs cas avec le même résultat

Comment une expression de commutateur peut-elle être écrite pour prendre en charge plusieurs cas retournant le même résultat?

Avec C # avant la version 8, un commutateur peut être écrit comme ceci:

var switchValue = 3;
var resultText = switchValue switch
{
    var x when (x >= 1 && x <= 3) => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

Quand j'utilise la version 8 C #, avec la syntaxe d'expression, c'est comme ça:

var switchValue = 3;
var resultText = switchValue switch
{
    1 => "one to three",
    2 => "one to three",
    3 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

Ma question est donc la suivante: comment transformer les cas 1, 2 et 3 en un seul interrupteur-boîtier-bras pour que la valeur n'ait pas besoin d'être répétée?

Mise à jour par suggestion de " Rufus L ":

Pour mon exemple donné, cela fonctionne.

var switchValue = 3;
var resultText = string.Empty;
switch (switchValue)
{
    case 1:
    case 2:
    case 3:
        resultText = "one to three";
        break;
    case 4:
        resultText = "four";
        break;
    case 5:
        resultText = "five";
        break;
    default:
        resultText = "unkown";
        break;
}

Mais ce n'est pas exactement ce que je veux accomplir. Il s'agit toujours d'un seul cas (avec une condition de filtre), et non de plusieurs cas donnant le même résultat de droite.


7 commentaires

Que voulez - vous accomplir? Les expressions de commutation ne sont pas des instructions de commutation et le dépassement est explicitement interdit. when est de toute façon beaucoup plus puissant que la chute. Vous pouvez utiliser Contains avec un tableau de valeurs si vous le souhaitez.


Au lieu d'utiliser 3 ou 4 ou 11 instructions case, vous pouvez utiliser une seule var x when listOfValues.Contains(x) et gérer autant de cas que vous le souhaitez


Je n'essaye pas de passer à travers des cas ici. Comme indiqué dans le premier bloc de code de mon cas de question 1, 2 et 3 exécutent exactement le même code de bras droit. Et aux fins de cette question, j'ai pris ici un exemple très très simple. Imaginez que le cas 1, 2, 3 évalue des trucs très différents et complexes comme la correspondance de motifs avec 'quand' etc.


le premier bloc de code passe par les cas. C'est le case 1: case 2: faire - ce sont des cas avec des blocs vides qui passent au suivant


En ce qui concerne les choses complexes, when peut gérer des cas beaucoup plus complexes qu'une simple échec. Le passage simple ne peut gérer que les contrôles d'égalité par rapport à une valeur codée en dur. Correspondance de modèle et when sont les mêmes dans les expressions de commutation. En fait, ce sont les instructions switch qui posent des problèmes de correspondance de modèles. Si vous cochez des langages fonctionnels comme F #, vous verrez que le cas d'utilisation principal (souvent le seul ) est les expressions de correspondance de modèle.


Avec la chute, j'ai voulu dire des corps non vides sur les caisses.


Je pense donc que la bonne façon de répondre à ma question est d'utiliser une expression dans la clause «quand».


4 Réponses :


74
votes

Je me suis mis à l'installer, mais je n'ai pas trouvé de moyen de spécifier plusieurs étiquettes de cas distinctes pour une seule section de commutateur avec la nouvelle syntaxe.

Cependant, vous pouvez créer une nouvelle variable qui capture la valeur, puis utiliser une condition pour représenter les observations qui devraient avoir le même résultat:

var resultText = switchValue switch
{
    var x when x > 0 && x < 4 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

C'est en fait plus concis si vous avez de nombreux cas à tester, car vous pouvez tester une plage de valeurs sur une seule ligne:

var resultText = switchValue switch
{
    var x when
        x == 1 ||
        x == 2 ||
        x == 3 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};


10 commentaires

La chute est explicitement interdite. La clause when est de toute façon beaucoup plus puissante que de tomber. Au lieu d'écrire 3 ou 10 instructions case , vous pouvez utiliser une seule var x when listOfValues.Contains(x)


Je pense donc que la bonne façon de répondre à ma question est d'utiliser une expression dans la clause «quand». Je marque donc votre réponse comme étant la bonne raison pour laquelle elle correspond à l'exigence. En particulier, le premier bloc de code est formaté pour avoir une sorte de cas par ligne.


@PanagiotisKanavos Je viens d'apprendre que si Fall Through est interdit, ce n'est pas Fall Through. Ceci est une correspondance multiple, qui est autorisée et compile: sharplab.io / ...


@Christopher auquel vous liez est une erreur dans les instructions de commutation. La question demandait comment faire de même avec les expressions de commutation, ce qui n'est pas autorisé.


@PanagiotisKanavos Sauf que ce n'est pas un échec, c'était mon argument. "Une seule section de commutateur dans une instruction de commutateur s'exécute. C # ne permet pas à l'exécution de continuer d'une section de commutateur à la suivante. Pour cette raison, le code suivant génère une erreur de compilateur, CS0163:" Le contrôle ne peut pas passer d'une étiquette de cas (<case label>) à un autre. "" - docs.microsoft.com/en-us/dotnet/csharp/language-reference/… | Il s'agissait d'une correspondance multiple par plusieurs étiquettes. Plutôt une liste séparée par des virgules comme en Python.


@PanagiotisKanavos Ce qui pourrait vous dérouter, c'est que beaucoup de Langauge ont implémenté Multiple Mathcing via Fall Through. Mais en C #, ils sont aussi différents que les délégués et les entiers. Ou des interfaces et des classes.


@Christopher Êtes-vous en train de dire que je devrais reformuler «passer» à autre chose? J'ai fait référence à l'utilisation de plusieurs déclarations de case dans une section de switch comme "tomber à travers" (comme @PanagiotisKanavos), mais je serais heureux d'utiliser un terme différent si cela prête à confusion.


@RufusL Je ne suis vraiment pas sûr de savoir quel est le terme approprié. Tout ce que je sais avec certitude, c'est que ce n'est pas un échec. Je pensais en fait cela moi-même, car j'avais mémorisé son implémentation via Falltrhough à mes débuts en Native C ++. Mais le gars qui m'a corrigé avait raison: ce n'est pas un échec. Cela ne peut pas être un échec. C'est autre chose. Et je ne suis pas sûr que la clause when soit juste un sous-ensemble pour la correspondance de modèle ou une troisième voie.


C'est suffisant. J'ai supprimé le terme et je l'ai simplement remplacé par plusieurs mots. :)


On dirait qu'ils vont arranger cela un peu avec la correspondance de modèles améliorée de C # 9.



14
votes

Malheureusement, cela semble être une lacune dans la syntaxe d'expression de commutateur, par rapport à la syntaxe d'instruction de commutateur. Comme d'autres affiches l'ont suggéré, la syntaxe var plutôt maladroite est votre seule vraie option.

Vous espériez peut-être pouvoir écrire:

ResultType tmp;
switch (switchValue) {
    case Type1 t1:
    case Type2 t2:
    case Type3 t3:
        tmp = ResultA;
        break;
    case Type4 t4:
        tmp = ResultB;
        break;
    case Type5 t5:
        tmp = ResultC;
        break;
};
return tmp;

Au lieu de cela, vous devrez écrire le code plutôt maladroit ci-dessous, avec le nom de type pulvérisé sur:

switchValue switch {
    var x when x is Type1 || x is Type2 || x is Type 3 => ResultA,
    Type4 t4 => ResultB,
    Type5 t5 => ResultC
};

Dans un exemple aussi simple, vous pouvez probablement vivre avec cette maladresse. Mais les exemples plus compliqués sont beaucoup moins habitables avec. En fait, mes exemples sont en fait une simplification d'un exemple tiré de notre propre base de code, où j'espérais convertir une instruction switch, avec environ six résultats mais plus d'une douzaine de cas de type, en une expression switch. Et le résultat était clairement moins lisible que l'instruction switch.

Mon point de vue est que si l'expression de commutateur nécessite des résultats partagés et fait plus de quelques lignes, alors vous feriez mieux de vous en tenir à une instruction de commutation. Huer! C'est plus bavard mais probablement une gentillesse envers vos coéquipiers.

switchValue switch {
    Type1 t1:
    Type2 t2:
    Type3 t3 => ResultA, // where the ResultX variables are placeholders for expressions.
    Type4 t4 => ResultB,
    Type5 t5 => ResultC
};


4 commentaires

Je sais que ce n'est qu'un exemple, mais il pourrait être nettoyé un peu en revenant directement des cas plutôt qu'en définissant tmp . De cette façon, vous pouvez également omettre les break .


Le commentaire de @ Ben est juste et, toutes choses étant égales par ailleurs, mon propre style aussi. Mais j'ai choisi de ne pas changer ma réponse car il n'y avait en fait aucun retour dans le message d'origine et je voulais que le calcul et le retour soient séparés proprement, donc le refactoring était plus clair.


Puisque vous n'utilisez pas les variables correspondantes, je vous recommande de mettre à jour votre exemple d'instruction switch pour utiliser le modèle de case Type2 _: pour les cas: case Type1 _: case Type2 _: etc.


@julealgon oui, l'utilisation des rejets est un meilleur style. Dans ce cas, j'utilisais les noms de variables car ResultA, ResultB, ResultC sont destinés à être des espaces réservés pour les expressions qui pourraient faire référence à t1, t2 ... t5. J'ai ajouté un commentaire pour rendre cela plus clair.



0
votes

Si votre type de commutateur est une énumération d'indicateur

[System.Flags]
public enum Values 
{
    One = 1, 
    Two = 2, 
    Three = 4,
    Four = 8,
    OneToThree = One | Two | Three
}

var resultText = switchValue switch
{
    var x when Values.OneToThree.HasFlag(x) => "one to three",
    Values.Four => "4",
    _ => "unknown",
};


0 commentaires

13
votes

C # 9 prend en charge les éléments suivants:

var switchValue = 3;
var resultText = switchValue switch
{
    var x when x.In(1, 2, 3) => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

Alternativement:

public static bool In<T>(this T val, params T[] vals) => vals.Contains(val);

La source


Pour les anciennes versions de C #, j'utilise la méthode d'extension suivante:

var switchValue = 3;
var resultText = switchValue switch
{
    >= 1 and <= 3 => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

comme ça:

var switchValue = 3;
var resultText = switchValue switch
{
    1 or 2 or 3 => "one, two, or three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};

C'est un peu plus concis que when x == 1 || x == 2 || x == 3 et a un ordre plus naturel que when new [] {1, 2, 3}.Contains(x) .


1 commentaires

Il alloue également un nouveau tableau int temporaire qui provoquera une pression GC s'il se trouve à l'intérieur d'une boucle, et est environ 100 fois plus lent que le 1 || 2 - mais cela n'a probablement pas d'importance pour la plupart du code