SoftAllan
SoftAllan

Reputation: 363

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?

Upvotes: 23

Views: 22740

Answers (7)

Mubarrat Hasan
Mubarrat Hasan

Reputation: 304

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

Upvotes: 3

anuith
anuith

Reputation: 739

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

Upvotes: 29

Ryan
Ryan

Reputation: 2149

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

Upvotes: 16

Julien Couvreur
Julien Couvreur

Reputation: 4973

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

Upvotes: 1

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131423

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.

Upvotes: 6

Yeldar Kurmangaliyev
Yeldar Kurmangaliyev

Reputation: 34199

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.

Upvotes: 19

SoftAllan
SoftAllan

Reputation: 363

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.

Upvotes: 2

Related Questions