78

I would like use a switch statement which takes several variables and looks like this:

switch (intVal1, strVal2, boolVal3)
{
   case 1, "hello", false:
      break;
   case 2, "world", false:
      break;
   case 2, "hello", false:

   etc ....
}

Is there any way to do something like this in C#? (I do not want to use nested switch statements for obvious reasons).

0

14 Answers 14

108

Yes. It's supported as of .NET 4.7 and C# 8. The syntax is nearly what you mentioned, but with some parenthesis (see tuple patterns).

switch ((intVal1, strVal2, boolVal3))
{
    case (1, "hello", false):
        break;
    case (2, "world", false):
        break;
    case (2, "hello", false):
        break;
}

If you want to switch and return a value there's a switch "expression syntax". Here is an example; note the use of _ for the default case:

string result = (intVal1, strVal2, boolVal3) switch
{
    (1, "hello", false) => "Combination1",
    (2, "world", false) => "Combination2",
    (2, "hello", false) => "Combination3",
    _ => "Default"
};

Here is a more illustrative example (a rock, paper, scissors game) from the MSDN article linked above:

public static string RockPaperScissors(string first, string second)
    => (first, second) switch
    {
        ("rock", "paper") => "rock is covered by paper. Paper wins.",
        ("rock", "scissors") => "rock breaks scissors. Rock wins.",
        ("paper", "rock") => "paper covers rock. Paper wins.",
        ("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
        ("scissors", "rock") => "scissors is broken by rock. Rock wins.",
        ("scissors", "paper") => "scissors cuts paper. Scissors wins.",
        (_, _) => "tie"
    };
Sign up to request clarification or add additional context in comments.

Comments

61

You can do this in C# 7 and higher with the when keyword:

switch (intVal1)
{
    case 1 when strVal2 == "hello" && !boolVal3:
        break;
    case 2 when strVal2 == "world" && !boolVal3:
        break;
    case 2 when strVal2 == "hello" && !boolVal3:
        break;
}

2 Comments

that's a certainly less involved and more readable solution than most presented here, when you don't have too many cases, thanks to this new language feature.
Indeed. Of course it could be made more concise by replacing boolVal3 == false with !boolVal3 (assuming it is a bool and not a nullable bool).
14

There is (was) no built-in functionality to do this in C#, and I don't know of any library to do this.

Here is an alternative approach, using Tuple and extension methods:

using System;

static class CompareTuple {
    public static bool Compare<T1, T2, T3>(this Tuple<T1, T2, T3> value, T1 v1, T2 v2, T3 v3) {
        return value.Item1.Equals(v1) && value.Item2.Equals(v2) && value.Item3.Equals(v3); 
    }
}

class Program {
    static void Main(string[] args) {
        var t = new Tuple<int, int, bool>(1, 2, false);
        if (t.Compare(1, 1, false)) {
            // 1st case
        } else if (t.Compare(1, 2, false)) {
            // 2nd case
        } else { 
            // default
        }
    }
}

This is basically doing nothing more than providing a convenient syntax to check for multiple values - and using multiple ifs instead of a switch.

4 Comments

For readers: it's now supported out of the box
This answer is pretty old now :)
I understand, not criticizing 🙂
Maybe we should be able to flag these pages as out-of-date, still visible, but it needs to look really different to a normal question, like a big warning at the top or something. It's confusing to have all these old answers to old questions coming up, C# has changed somewhat in 10 years.
12

Let's look at this another way. If you have:

  • Very specific combinations you want to check for;
  • No comparisons to do;
  • A default handler for every non-matching case;
  • All primitive/value types (int, bool, string, etc.)

Then you can use a look-up table instead, which has a similar execution speed to the switch statement but not quite as efficient (since it needs to calculate hashes). Still, it's probably good enough. And it gives you the opportunity to name cases, to make this combinatorial explosion slightly less confusing and unmaintainable.

A code example:

private static readonly Tuple<int, int, bool> NameOfCase1 = 
    Tuple.Create(1, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase2 =
    Tuple.Create(2, 1, false);
private static readonly Tuple<int, int, bool> NameOfCase3 =
    Tuple.Create(2, 2, false);

private static readonly Dictionary<Tuple<int, int, bool>, string> Results =
    new Dictionary<Tuple<int, int, bool>, string>
{
    { NameOfCase1, "Result 1" },
    { NameOfCase2, "Result 2" },
    { NameOfCase3, "Result 3" }
};

public string GetResultForValues(int x, int y, bool b)
{
    const string defaultResult = "Unknown";
    var lookupValue = Tuple.Create(x, y, b);
    string result;
    Results.TryGetValue(lookupValue, out result);
    return defaultResult;
}

If you need to actually execute a function or method for each case then you can use a result type (dictionary value) of Action<T> or Func<T> instead.

Note that I'm using Tuple<T1,T2,T3> here because it already has all of the hash code logic built in. The syntax is a little awkward in C# but if you want, you can implement your own lookup class and just override Equals and GetHashCode.

1 Comment

+1. You went with Tuple and I went with the separate lookup class. I think I like your idea better.
7

My downright crazy take on this:

class Program
{
    static void Main(string[] args)
    {
        var i = 1;
        var j = 34;
        var k = true;
        Match(i, j, k).
            With(1, 2, false).Do(() => Console.WriteLine("1, 2, 3")).
            With(1, 34, false).Do(() => Console.WriteLine("1, 34, false")).
            With(x => i > 0, x => x < 100, x => x == true).Do(() => Console.WriteLine("1, 34, true"));

    }

    static Matcher<T1, T2, T3> Match<T1, T2, T3>(T1 t1, T2 t2, T3 t3)
    {
        return new Matcher<T1, T2, T3>(t1, t2, t3);
    }
}

public class Matcher<T1, T2, T3>
{
    private readonly object[] values;

    public object[] Values
    {
        get { return values; }
    }

    public Matcher(T1 t1, T2 t2, T3 t3)
    {
        values = new object[] { t1, t2, t3 };
    }

    public Match<T1, T2, T3> With(T1 t1, T2 t2, T3 t3)
    {
        return new Match<T1, T2, T3>(this, new object[] { t1, t2, t3 });
    }

    public Match<T1, T2, T3> With(Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        return new Match<T1, T2, T3>(this, t1, t2, t3);
    }
}

public class Match<T1, T2, T3>
{
    private readonly Matcher<T1, T2, T3> matcher;
    private readonly object[] matchedValues;
    private readonly Func<object[], bool> matcherF; 

    public Match(Matcher<T1, T2, T3> matcher, object[] matchedValues)
    {
        this.matcher = matcher;
        this.matchedValues = matchedValues;
    }

    public Match(Matcher<T1, T2, T3> matcher, Func<T1, bool> t1, Func<T2, bool> t2, Func<T3, bool> t3)
    {
        this.matcher = matcher;


        matcherF = objects => t1((T1)objects[0]) && t2((T2)objects[1]) && t3((T3)objects[2]);
    }

    public Matcher<T1, T2, T3> Do(Action a)
    {
        if(matcherF != null && matcherF(matcher.Values) || matcher.Values.SequenceEqual(matchedValues))
            a();

        return matcher;
    }
}

1 Comment

Interesting quasi-functional version, although not type safe (and potentially not comparison safe). I might use IComparable or IEquatable instead of just object. A better version would use generics.
5

You could convert to a string:

switch (intVal1.ToString() + strVal2 + boolVal3.ToString())
{
   case "1helloFalse":
      break;
   case "2worldFalse":
      break;
   case "2helloFalse":

   etc ....
}

I think the question that comes to play, though is whether or not there's a better way of defining the logic. For instance, let's say you're trying to figure out who knows superman. We could do the check like this:

switch (first + last)
{
   case "ClarkKent":
   case "LoisLane":
      // YES
      break;
   default;
      // Sadly, no
      break;
}

But what happens when you get some other guy named Clark Kent? Really couldn't you have some other value that you determine this logic based on, ie bool KnowsSuperman?

The idea being, a switch statement is used to determine logic based off a single set of choices. If there are multiple values you're trying to switch off of, then the logic could get insanely difficult to maintain down the line.

Another example would be if you need to group people into several groups and perform some logic depending on the group they're in. You could code it up to say, if you're Bob, Jeff, Jim, or Sally, you're in group A, but what if you need to add someone else to group A? You'd have to change the code. Instead, you could create an extra property called Group, which could be an enum or string, which you could use to specify which group someone is in.

Comments

3

Update for 2018. As of C#7.0 Microsoft introduced the "when" clause for switches making it effectively possible to extend switch cases with additional conditions.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch#the-case-statement-and-the-when-clause

Comments

2

I'm not sure which C# version this appeared it but you can do this:

var x = 22;
var y = 33;
var z = 44;

switch (x, y, z) {

    case (33, 33, 33):
        WriteLine("This should not run");
        break;

    case (22, 33, 44):
        WriteLine("This should run");
        break;
}

Comments

1

Per the C# language specification, the switch statement expression must resolve to one of sbyte, byte, sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or an enum-type. This means you cannot switch on Tuple or other higher-order types.

You could try to pack the values together, assuming there is room. For example, suppose each of the integers is guaranteed to be in the range 0..9.

switch (intVal1 * 100 + intVal2 * 10 + (boolVal3 ? 1 : 0))
{
case 100: /* intVal1 = 1, intVal2 = 0, boolVal3 = false */ ... break;
case 831: /* intVal1 = 8, intVal2 = 3, boolVal3 = true */ ... break;
}

5 Comments

I was looking at it more as a puzzle.
If you create an enum for the values, this is VERY readable. Plus, as soon as I finished reading the question, the first thought in my head was using bitwise statements and flags.
It's a possibility I also considered, however it is not nice :(
Especially when you change the question to now include a string as the second "parameter", when it was originally an int.
If it's all enums, you can write the case in a more readable way using + as it will still be evaluated at compile time. E.g. for enums Day and Month: Day d = Day.Wednesday; Month m = Month.February; switch ((int)d + 8*(int)m) { case (int)Day.Monday + 7*(int)Month.January: ... break; case (int)Day.Wednesday + 7*(int)Month.February: ... break; ...}
0
if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}

Comments

0

I do this kind of thing with lists or arrays. If you can enumerate the possible conditions (which you obviously can if you're wanting to do a multi-value switch), then build a lookup table with a multi-part key and an Action or Func<T> as the value.

A simple version would use a Dictionary:

class LookupKey: IComparable<LookupKey>
{
    public int IntValue1 { get; private set; }
    public int IntValue2 { get; private set; }
    public bool BoolValue1 { get; private set; }
    public LookupKey(int i1, int i2, bool b1)
    {
        // assign values here
    }
    public int Compare(LookupKey k1, LookupKey k2)
    {
        return k1.IntValue1 == k2.IntValue1 &&
               k1.IntValue2 == k2.IntValue2 &&
               k1.BoolValue1 == k2.BoolValue1;
    }
    public int GetHashCode()
    {
        return (19 * IntValue1) + (1000003 * IntValue2) + (BoolValue1) ? 0 : 100000073;
    }
    // need to override Equals
}

And your dictionary:

static readonly Dictionary<LookupKey, Action<object>> LookupTable;

You can then populate the dictionary at startup, and then a lookup becomes a simple matter of:

Action<object> MethodToCall;
if (LookupTable.TryGetValue(new LookupKey(i1, i2, b1), out MethodToCall)
    MethodToCall(theData);
else
    // default action if no match

It's a bit of code to set up, but it's very quick in execution.

Comments

0
//.Net Core 3.1
    class Convertors
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Convertors.ConvertAny("m","cm", 10));
            Console.ReadKey();
        }
        public static double MToCM(double value)
        {
            return value * 100;
        }
        public static double ConvertAny(string srcUOM, string tgtUOM, double value)
        {
            switch (srcUOM.ToLower(), tgtUOM.ToLower())
            {
                case ("m", "cm"): return Convertors.MToCM(value);
                default: throw new NotImplementedException();
            }
        }
    }

Comments

-1

For those who got here from their favorite search engine looking for the opposite (providing multiple responses based on a switch), you can leverage Stephen Kennedy's excellent answer regarding tuple-based positional patterns and switch expressions in C# 8.

var (age, favoriteFood) = FirstName switch
{
    "Jerry" => (35, Foods.Cereal),
    "Elaine" => (28, Foods.TheSalad.Big),
    "George" => (29, Foods.Pastrami.OnRye),
    "Kramer" => (39, Foods.Peach),
    "Newman" => (35, Candy.Chunky),
    _ => (-1, Foods.Other)
}

You might have to do some casting if your return types inside the returned Tuple are inconsistent.

You can also combine our answers to switch on a Tuple of multiple variables and return a Tuple of multiple variables.

Comments

-2

You cannot do that in C# as far as I know.

But you can do this from MSDN:

The following sample shows that fall through from one case label to another is allowed for empty case labels:

 switch(n) 
        {
            case 1:
            case 2: 
            case 3: 
                Console.WriteLine("It's 1, 2, or 3.");
                break; 
        default: 
            Console.WriteLine("Not sure what it is.");
            break; 
        }

6 Comments

Hi, yes, I can, but this switch takes only one variable, I want to process three.
@BanditoBunny - Then the answer is no you cannot do that.
-1: correct answer, except for your switch. Remove it and I'll remove the downvote.
Wrong != Possible - What you want isn't possible, so my solution isn't wrong - it just doesn't fit your combinatorial explosion :) (to put it nicely).
@JonH - This does not even come close to doing what the author wants. Of course what he wants both cannot be done per the specification of the language nor should it. This does something entirely different, Raymond Chen's horrible looking code, came close to what the author wanted.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.