C# 中的多变量 switch 语句

发布于 2024-12-12 19:21:07 字数 460 浏览 6 评论 0 原文

我想使用一个带有多个变量的 switch 语句,如下所示:

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

   etc ....
}

Is有什么方法可以在 C# 中执行类似的操作吗? (出于显而易见的原因,我不想使用嵌套的 switch 语句)。

.net 开发团队通过实施这种恐惧来回答了这个问题: C# 中的多变量 switch 语句

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

The question was answered by .net dev team by implementing of exactly this fearture: Multi-variable switch statement in C#

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

老娘不死你永远是小三 2024-12-19 19:21:07

是的。从 .NET 4.7 和 C# 8 开始支持它。语法几乎与您提到的相同,但带有一些括号(请参阅 元组模式)。

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

如果您想切换并返回一个值,可以使用 switch“表达式语法”。这是一个例子;请注意默认情况下使用 _

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

下面是来自上面链接的 MSDN 文章的更具说明性的示例(石头、剪刀、布游戏):

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

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"
    };
ゃ懵逼小萝莉 2024-12-19 19:21:07

您可以在 C# 7 及更高版本中使用 when 关键字:

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

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;
}
凉世弥音 2024-12-19 19:21:07

C# 中没有内置功能可以执行此操作,而且我不知道有任何库可以执行此操作。

这是一种替代方法,使用 Tuple 和扩展方法:

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

这基本上只是提供一种方便的语法来检查多个值 - 并使用多个 if 而不是一个开关

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.

巴黎盛开的樱花 2024-12-19 19:21:07

让我们换个角度来看这个问题。如果您有:

  • 您想要检查的非常特定组合;
  • 无需比较;
  • 每个不匹配情况的默认处理程序;
  • 所有基元/值类型(intboolstring 等)

然后您可以使用查找表

代码示例:

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

如果您需要为每种情况实际执行一个函数或方法,那么您可以使用 ActionFunc 结果类型(字典值)代码> 代替。

请注意,我在这里使用 Tuple 因为它已经内置了所有哈希代码逻辑。该语法在 C# 中有点尴尬,但如果您愿意,您可以实现您自己的查找类,只需覆盖 EqualsGetHashCode 即可。

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.

迷离° 2024-12-19 19:21:07

我对此的看法简直是疯狂:

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

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;
    }
}
╰つ倒转 2024-12-19 19:21:07

您可以转换为字符串:

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

   etc ....
}

我认为出现的问题是是否有更好的方法来定义逻辑。例如,假设您想找出谁认识超人。我们可以这样进行检查:

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

但是当你找到另一个叫克拉克·肯特的人时会发生什么?你真的不能有一些其他值来确定这个逻辑,即 bool KnowsSuperman 吗?

这个想法是, switch 语句用于根据一组选择来确定逻辑。如果您尝试关闭多个值,那么逻辑可能会变得非常难以维护。

另一个例子是,如果您需要将人们分为几个组,并根据他们所在的组执行一些逻辑。您可以编写代码来表示,如果您是 Bob、Jeff、Jim 或 Sally,那么您就是在 A 组中,但是如果您需要将其他人添加到 A 组中怎么办?你必须更改代码。相反,您可以创建一个名为 Group 的额外属性,它可以是枚举或字符串,您可以使用它来指定某人所在的组。

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.

懷念過去 2024-12-19 19:21:07

2018 年更新。从 C#7.0 开始,Microsoft 为开关引入了“when”子句,使得可以有效地使用附加条件扩展开关情况。

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

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

心房的律动 2024-12-19 19:21:07

我不确定这出现在哪个 C# 版本中,但你可以这样做:

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

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;
}
悍妇囚夫 2024-12-19 19:21:07

根据 C# 语言规范,switch 语句表达式必须解析为 sbyte、byte、sbyte、byte、short、ushort、int、uint、long、ulong、char、string 或枚举类型。这意味着您无法打开 Tuple 或其他高阶类型。

假设有空间,您可以尝试将这些值打包在一起。例如,假设每个整数都保证在 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;
}

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;
}
浪荡不羁 2024-12-19 19:21:07
if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}
if (a == 1 && b == 1) {}
else if (a == 1 && b == 2) {}
else if (a == 2 && b ==2) {}
梦萦几度 2024-12-19 19:21:07

我用列表或数组做这种事情。如果您可以枚举可能的条件(如果您想要进行多值切换,那么显然可以),然后使用多部分键和 Action 构建一个查找表>Func 作为值。

一个简单的版本将使用 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
}

以及您的字典:

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

然后您可以在启动时填充字典,然后查找就变得很简单:

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

需要设置一些代码,但它非常简单执行速度快。

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.

请爱~陌生人 2024-12-19 19:21:07
//.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();
            }
        }
    }
//.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();
            }
        }
    }
花落人断肠 2024-12-19 19:21:07

据我所知,你不能在 C# 中做到这一点。

但您可以从 MSDN 中执行此操作:

以下示例显示,对于空 case 标签,允许从一个 case 标签跌落到另一个 case 标签:

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

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; 
        }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文