How to use c# tuple value types in a switch statement
Asked Answered
H

7

23

I'm using the new tuple value types in .net 4.7. In this example I am trying to make a switch statement for one or more cases of a tuple:

using System;
namespace ValueTupleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            (char letterA, char letterB) _test = ('A','B');
            Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

            switch (_test)
            {
                case ('A', 'B'):
                    Console.WriteLine("Case ok.");
                    break;
            }

        }
    }
}

This does not compile unfortunately.

How do I take a tuple and make cases in a switch statement correctly?

Haakon answered 4/6, 2017 at 15:20 Comment(4)
You can't use a tuple as a switch value, switch only accepts constant values.Kraken
@Kraken Not only, actually.Brnaby
@YeldarKurmangaliyev If you mean the new sintax to use types, the types can be considered also to be constant.Kraken
@Kraken I meant when syntax. case Rectangle r when r.Height == r.Width is not very constant :)Brnaby
U
26

Just a note for anyone who stumbled upon this question.

C# 8.0 introduces switch expressions which is really useful in this situation.

Now you can do something like this :

var test = ('A', 'B');
var result = test switch
{
    ('A', 'B') => "OK",
    ('A',   _) => "First part OK",
    (  _, 'B') => "Second part OK",
    _ => "Not OK",
};

Console.WriteLine(result);

Try in .NET fiddle

Unaffected answered 22/6, 2020 at 7:3 Comment(2)
In case you get the error: Only assignment, call, increment, decrement, and new object expressions can be used as a statement, the above code can be modified to var test = ('A', 'B') switch { ('A', 'B') => "OK", ('A', _) => "First part OK", (_, 'B') => "Second part OK", _ => "Not OK", }; Console.WriteLine(test);Hau
Thanks, @NadeemYousuf-AIS I didn't try it. Edited the answer above.Unaffected
B
19

Answering your question technically, you could use when to check tuple's values:

(char letterA, char letterB) _test = ('A', 'B');
Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

switch (_test)
{
    case var tuple when tuple.letterA == 'A' && tuple.letterB == 'B':
        Console.WriteLine("Case ok.");
        break;
    case var tuple when tuple.letterA == 'D' && tuple.letterB == '\0':
        Console.WriteLine("Case ok.");
        break;
}

However, consider using if version because it may be a more readable and understandable solution.

Another side of this question is single responsibility. Your methods knows what do A and B, D and \0 characters mean which breaks the single-responsibility principle.
In terms of OOP, it is better to separate this knowledge from you main code into a separate method.
Something like that could make code a little cleaner:

private static bool IsCaseOk(char a, char b) 
{
    return (a == 'A' && b == 'B') || (a == 'D' && b == '\0'); // any logic here
}

public static void Main() 
{
    (char letterA, char letterB) _test = ('A', 'B');
    Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

    if (IsCaseOk(_test.letterA, _test.letterB)) {
        Console.WriteLine("Case ok.");
    } else {
        Console.WriteLine("Case not ok.");
    }
}

If these letters have any meaning in your domain, then probably it is a better idea to even create a class with two char properties and encapsulate this logic there.

Brnaby answered 5/6, 2017 at 6:56 Comment(1)
Pattern matching is a functional, not an OOP concept. Matching against a value is perfectly OK and doesn't break SRP. Check the Binding to Values section in Match Expression. If anything, this makes it easier to separate responsibilities. By breaking the switch into two parts, the Single Responsibility became two different pieces of codeGraceless
T
16

C# 7.3 introduces tuple equality which means your initial idea in the question is almost correct. You just need to capture the value you are comparing like this:

var _test = ('A','B');
switch (_test)
{
   case var t when t == ('A', 'B'):
   Console.WriteLine("Case ok.");
   break;
}
Technocracy answered 8/6, 2018 at 20:46 Comment(1)
if the comparison is marked as error, then alt+enter / "uprade to c# 7.3"Shockley
G
6

There's nothing wrong with using tuples or pattern matching. If anything, these allow you to write cleaner code and avoid spreading your logic to multiple methods.

C# 7 doesn't allow you to match against tuple values yet. You can't compare two tuples with the == operator either. What you can do, is use Equals two compare two value tuples:

 if (_test.Equals(('A','B'))
{
    Console.WriteLine("Case A ok.");
}
else if (_test.Equals(('D','\0'))
{
    Console.WriteLine("Case D ok.");
}

It would seem that you are trying to create a state matchine for a parser(?), that matches specific patterns. This can work with pattern matching if you specify different state classes instead of using a single tuple for all cases.

All you need to do is specify a single IState interface without methods, and use it in all state classes, eg:

interface IMyState {};
public class StateA:IMyState{ public string PropA{get;set;} };
public class StateD:IMyState{ public string PropD{get;set;} };

...
IMyState _test= new StateD(...);

switch (_test)
{
    case StateA a: 
        Console.WriteLine($"Case A ok. {a.PropA}");
        break;
    case StateD d: 
        Console.WriteLine($"Case D ok. {d.PropD}");
        break;
    default :
        throw new InvalidOperationException("Where's my state ?");
}

The a, d variables are strongly typed, which means you don't have to add anything to the IState interface. It's there only to satisfy the compiler.

By using structs instead of classes for the state types, you'll get the same memory benefits you would with tuples. If you want to use deconstruction, you can add a Deconstruct method to each type, or use Deconstruct extension methods in a separate static class.

Graceless answered 6/6, 2017 at 15:24 Comment(0)
B
3

If one is ok and two is not ok in tuple then you can use the _ sign to discard one.

switch (_test)
{
    case ('A', 'B'):
        Console.WriteLine("Case A B ok.");
        break;
    case ('C', 'D'):
        Console.WriteLine("Case C D ok.");
        break;
    case ('A', _):
        Console.WriteLine("Case A ok.");
        break;
    case (_, 'B'):
        Console.WriteLine("Case B ok.");
        break;
    default:
        Console.WriteLine("Nothing ok.");
        break;
}

Update - C# 8.0

You can use the switch expression to proceed from the doc.

Console.WriteLine(_test switch
{
    ('A', 'B') => "Case A B ok.",
    ('C', 'D') => "Case C D ok.",
    ('A', _)   => "Case A ok.",
    (_, 'B')   => "Case A ok.",
    _          => "Nothing ok."
});
Barbarossa answered 23/2, 2022 at 5:7 Comment(0)
H
2

Thanks for the replies.

I decided to drop the use of a switch statement and go for the old if/else statement.

using System;

namespace ValueTupleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            (char letterA, char letterB) _test = ('A','B');
            Console.WriteLine($"Letter A: '{_test.letterA}', Letter B: '{_test.letterB}'");

            if (_test.letterA == 'A' && _test.letterB == 'B')
            {
                Console.WriteLine("Case A ok.");
            }
            else if (_test.letterA == 'D' && _test.letterB == '\0')
            {
                Console.WriteLine("Case D ok.");
            }

        }
    }
}

This way I can decide if I want to test for all values in the tuple and in the order that I need. It shouldn't be much different in performance I think.

If there are another way of using tuples with a switch statement please feel free to give an example.

Haakon answered 5/6, 2017 at 6:50 Comment(2)
You don't need to give up on tuples, use _test.Equals(('A', 'B'))Graceless
It looks like you are trying to implement a state machine for a parser? You can use pattern matching for this, just not with tuplesGraceless
P
1

The syntax for case (...): is reserved for future kinds of patterns. See positional patterns described in the C# language feature specification: https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md#positional-pattern

Punishment answered 3/7, 2017 at 5:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.