Multi-variable switch statement in C#

喜欢而已 提交于 2019-11-27 17:14:12

问题


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).


回答1:


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.




回答2:


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

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



回答3:


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.




回答4:


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



回答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.




回答6:


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://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/switch#the-case-statement-and-the-when-clause




回答7:


If you want to switch over multiple inputs and return a value, in C# 8 you can use tuple patterns. This is a form of pattern matching switch expression which takes multiple values as an input in the form of a tuple. Here is an example using your inputs; 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"
    };



回答8:


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



回答9:


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



回答10:


if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}



回答11:


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.



来源:https://stackoverflow.com/questions/7967523/multi-variable-switch-statement-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!