'How to use c# tuple value types in a switch statement

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?



Solution 1:[1]

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

Solution 2:[2]

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.

Solution 3:[3]

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

Solution 4:[4]

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.

Solution 5:[5]

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.

Solution 6:[6]

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

Solution 7:[7]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4
Solution 5 SoftAllan
Solution 6
Solution 7 Julien Couvreur