C# “switch expression” – manchmal muss man aufpassen

Wie bei vielen Dingen ist es im Nachhinein klar warum etwas Komisches passiert aber im ersten Moment überrascht es dann doch. Hier ein kleiner Aufreger bei der eigentlich sehr praktischen “switch expression” von C#.

Angefangen hat es mit einer Extension Methode auf JsonNode, die im direkten Spielen mit der JSON Deserialisierung für einfache Datentypen direkt die nativen Werte liefern soll.

public static object? ToJsonScalar(this JsonNode node)
{
    switch (node.GetValueKind())
    {
        case JsonValueKind.True: return true;
        case JsonValueKind.False: return false;
        case JsonValueKind.Null: return null;
        case JsonValueKind.String: return node.GetValue<string>();
        case JsonValueKind.Number: return node.GetValue<double>();
        default: return node;
    }
}

Benutzt wird das dann wie folgt und das liefert auch die richtige Ausgabe (System.Double):

var num = JsonNode.Parse("3.14")!;

Console.WriteLine(num.ToJsonScalar()?.GetType());

Die Überraschung gab es dann bei der manuellen Umstellung auf eine “switch expression” – die automatische Konvertierung macht es besser, aber hier geht es ja ums Prinzip:

public static object? ToJsonScalar(this JsonNode node)
    => node.GetValueKind() switch
    {
        JsonValueKind.True => true,
        JsonValueKind.False => false,
        JsonValueKind.Null => null,
        JsonValueKind.String => node.GetValue<string>(),
        JsonValueKind.Number => node.GetValue<double>(),
        _ => node,
    };

Sieht gleich aus? Ist es aber nicht, die Ausgabe lautet nun:

System.Text.Json.Nodes.JsonValuePrimitive`1[System.Double]

Was ist passiert? Ähnlich wie ein ternärer Ausdruck (?:) muss die “switch expression” einen einzigen Datentyp melden. Der Compiler entscheidet sich hier für den Default (_) und das ist ein JsonNode. Da alle anderen Datentypen einen impliziten Casting Operator haben, wird im Beispiel aus der Zahl direkt wieder ein JsonNode – genauer halt ein JsonValuePrimitive<double>.

Die automatische Konvertierung des switch Statements erzeugt einen (object) Cast bei JsonValueKind.Number, ich habe die Variante auf dem Default bevorzugt:

public static object? ToJsonScalar(this JsonNode node)
    => node.GetValueKind() switch
    {
        JsonValueKind.True => true,
        JsonValueKind.False => false,
        JsonValueKind.Null => null,
        JsonValueKind.String => node.GetValue<string>(),
        JsonValueKind.Number => node.GetValue<double>(),
        _ => (object?)node,
    };

Happy Coding

Jochen