c # 8 Schalterausdruck mehrere Fälle mit demselben Ergebnis

74

Wie muss ein Schalterausdruck geschrieben werden, um mehrere Fälle zu unterstützen, die dasselbe Ergebnis zurückgeben?

Mit c # vor Version 8 kann ein Schalter wie folgt geschrieben werden:

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

Wenn ich die Ausdruckssyntax von c # Version 8 verwende, ist dies wie folgt:

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

Meine Frage lautet also: Wie werden die Fälle 1, 2 und 3 zu nur einem Schalter-Fall-Arm, damit der Wert nicht wiederholt werden muss?

Update per Vorschlag von " Rufus L ":

Für mein gegebenes Beispiel funktioniert dies.

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

Aber es ist nicht genau das, was ich erreichen möchte. Dies ist immer noch nur ein Fall (mit einer Filterbedingung), nicht mehrere Fälle, die zum gleichen Ergebnis auf der rechten Seite führen.

Huzle
quelle
Was tun wollen Sie erreichen? Switch-Ausdrücke sind keine Switch-Anweisungen und das Durchfallen ist ausdrücklich verboten. whenist sowieso weitaus mächtiger als fall-through. Sie können Containsmit einem Array von Werten verwenden, wenn Sie möchten.
Panagiotis Kanavos
Anstatt 3 oder 4 oder 11 var x when listOfValues.Contains(x)
Fallanweisungen zu verwenden,
1
Ich versuche hier nicht, über Fälle hinwegzukommen. Wie im ersten Codeblock meiner Frage gezeigt, führen die Fälle 1, 2 und 3 genau den gleichen Code für den rechten Arm aus. Und zum Zweck dieser Frage habe ich hier ein sehr sehr einfaches Beispiel genommen. Stellen Sie sich vor, Fall 1,2,3 würde sehr unterschiedliche und komplexe Dinge wie Mustervergleich mit 'wann' usw.
bewerten
Der erste Codeblock fällt durch Fälle. Das ist, was zu case 1: case 2:tun ist - es sind Fälle mit leeren Blöcken, die bis zum nächsten
durchfallen
Komplexe Dinge whenkönnen weitaus komplexere Fälle behandeln als einfaches Durchfallen. Der einfache Durchfall kann nur Gleichheitsprüfungen gegen einen fest codierten Wert durchführen. Mustervergleich und whensind in Schalterausdrücken gleich. Tatsächlich sind es switch- Anweisungen , die Probleme beim Pattern Matching verursachen. Wenn Sie funktionale Sprachen wie F # überprüfen, werden Sie feststellen, dass der primäre Anwendungsfall (häufig der einzige ) Mustervergleichsausdrücke sind.
Panagiotis Kanavos

Antworten:

73

Ich habe es installiert, aber ich habe keine Möglichkeit gefunden, mehrere separate Fallbezeichnungen für einen einzelnen Switch-Abschnitt mit der neuen Syntax anzugeben.

Sie können jedoch eine neue Variable erstellen, die den Wert erfasst, und dann eine Bedingung verwenden, um die Fälle darzustellen, die dasselbe Ergebnis haben sollten:

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

Dies ist tatsächlich prägnanter, wenn Sie viele Fälle testen müssen, da Sie einen Wertebereich in einer Zeile testen können:

var resultText = switchValue switch
{
    var x when x > 0 && x < 4 => "one to three",
    4 => "four",
    5 => "five",
    _ => "unknown",
};
Rufus L.
quelle
Durchfallen ist ausdrücklich verboten. Die whenKlausel ist weitaus mächtiger als ohnehin durchzufallen. Anstatt 3 oder 10 caseAussagen zu schreiben , können Sie eine einzelne verwendenvar x when listOfValues.Contains(x)
Panagiotis Kanavos
Daher denke ich, dass der richtige Weg für meine Frage darin besteht, einen Ausdruck in der 'when'-Klausel zu verwenden. Daher markiere ich Ihre Antwort als die richtige Ursache, die der Anforderung entspricht. Insbesondere der erste Codeblock ist so formatiert, dass er einen Fall pro Zeile enthält.
Huzle
1
@PanagiotisKanavos Außer es ist kein Durchfall, was mein Punkt war. "Nur ein Switch-Abschnitt in einer switch-Anweisung wird ausgeführt. Mit C # kann die Ausführung nicht von einem Switch-Abschnitt zum nächsten fortgesetzt werden. Aus diesem Grund generiert der folgende Code den Compilerfehler CS0163:" Die Steuerung kann nicht von einer Fallbezeichnung durchfallen (<case case>) zu einem anderen. "" - docs.microsoft.com/en-us/dotnet/csharp/language-reference/… | Dies wurde durch mehrere Labels mehrfach abgeglichen. Eher als eine durch Kommas getrennte Liste wie in Python.
Christopher
1
@PanagiotisKanavos Was Sie verwirren könnte, ist, dass viele Langauge Multiple Mathcing über Fall Through implementiert haben . In C # sind diese jedoch so unterschiedlich wie Delegaten und Ganzzahlen. Oder Schnittstellen und Klassen.
Christopher
1
@ RufusL Ich bin mir nicht sicher, ob der richtige Begriff überhaupt ist. Alles was ich mit Sicherheit weiß ist, dass es nicht durchfällt. Ich dachte tatsächlich, dass ich selbst, als ich mir gemerkt hatte, dass es in meinen frühen Native C ++ - Tagen über Falltrhough implementiert wurde. Aber der Typ, der mich korrigiert hat, hatte Recht: Es ist kein Durchfall. Es kann nicht durchfallen. Es ist etwas anderes. Und ich bin mir nicht sicher, ob die when-Klausel nur eine Teilmenge für den Mustervergleich oder ein dritter Weg insgesamt ist.
Christopher
14

Leider scheint dies ein Mangel in der Switch-Ausdruckssyntax im Vergleich zur Switch-Anweisungssyntax zu sein. Wie andere Poster vorgeschlagen haben, ist die eher ungeschickte varSyntax Ihre einzige echte Option.

Sie hätten also gehofft, Sie könnten schreiben:

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

Stattdessen müssen Sie den ziemlich umständlichen Code unten mit dem gesprühten Typnamen schreiben:

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

In einem so einfachen Beispiel können Sie wahrscheinlich mit dieser Unbeholfenheit leben. Aber kompliziertere Beispiele sind viel weniger lebenswert. Tatsächlich sind meine Beispiele eine Vereinfachung eines Beispiels aus unserer eigenen Codebasis, in der ich gehofft hatte, eine switch-Anweisung mit ungefähr sechs Ergebnissen, aber über einem Dutzend Typfällen in einen switch-Ausdruck umzuwandeln. Und das Ergebnis war deutlich weniger lesbar als die switch-Anweisung.

Meiner Ansicht nach sollten Sie sich besser an eine switch-Anweisung halten, wenn der switch-Ausdruck gemeinsame Ergebnisse benötigt und mehr als ein paar Zeilen lang ist. Boo! Es ist ausführlicher, aber wahrscheinlich eine Freundlichkeit für Ihre Teamkollegen.

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;
sfkleach
quelle
4
Ich weiß, dass es nur ein Beispiel ist, aber es könnte ein bisschen aufgeräumt werden, indem man direkt von den Fällen zurückkehrt, anstatt es einzustellen tmp. Auf diese Weise können Sie auch das breaks weglassen .
Ben
1
@ Bens Kommentar ist fair und, wenn alle Dinge gleich sind, auch mein eigener Stil. Aber ich habe mich entschieden, meine Antwort nicht zu ändern, weil es im ursprünglichen Beitrag tatsächlich keine Rückgabe gab und ich wollte, dass die Berechnung und die Rückgabe sauber getrennt werden, damit das Refactoring klarer wurde.
sfkleach
Da Sie die angepassten Variablen nicht verwenden, würde ich Ihre switch - Anweisung Beispiel Aktualisierung empfehlen den Ablagemuster für die Fälle zu verwenden: case Type1 _:, case Type2 _:usw.
julealgon
@ Julealgon Ja, das Verwenden von Rückwürfen ist ein besserer Stil. In diesem Fall habe ich die Variablennamen verwendet, da ResultA, ResultB, ResultC Platzhalter für Ausdrücke sein sollen, die auf t1, t2 ... t5 verweisen könnten. Ich habe einen Kommentar hinzugefügt, um dies klarer zu machen.
sfkleach
12

C # 9 unterstützt Folgendes:

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

Alternative:

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

Quelle


Für ältere Versionen von C # verwende ich die folgende Erweiterungsmethode:

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

so was:

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

Es ist etwas prägnanter als when x == 1 || x == 2 || x == 3und hat eine natürlichere Reihenfolge als when new [] {1, 2, 3}.Contains(x).

Zev Spitz
quelle
2
Außerdem wird ein neues temporäres Int-Array zugewiesen, das GC-Druck verursacht, wenn sich dieser innerhalb einer Schleife befindet und etwa 100-mal langsamer als 1 || ist 2 - aber das ist wahrscheinlich für die meisten Codes nicht wichtig
Orion Edwards
0

Wenn Ihr Switch-Typ eine Flag-Aufzählung ist

[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",
};
pcchan
quelle