【问题标题】:Multi-variable switch statement in C#C#中的多变量switch语句
【发布时间】:2011-12-19 12:46:15
【问题描述】:

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

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

   etc ....
}

有没有办法在 C# 中做这样的事情? (出于显而易见的原因,我不想使用嵌套的 switch 语句)。

.net 开发团队通过实现这个恐惧来回答这个问题:Multi-variable switch statement in C#

【问题讨论】:

    标签: c# .net switch-statement logic switch-expression


    【解决方案1】:

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

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

    【讨论】:

    • 当您没有太多案例时,这肯定比这里介绍的大多数解决方案涉及更少且更具可读性,这要归功于这个新的语言功能。
    • 确实如此。当然,可以通过将 boolVal3 == false 替换为 !boolVal3 来使其更简洁(假设它是一个 bool 而不是可为空的 bool)。
    【解决方案2】:

    是的。从 .NET 4.7 和 C# 8 开始支持它。语法与您提到的差不多,但带有一些括号(请参阅tuple patterns)。

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

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

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

    【讨论】:

    • 这或多或少是我当时正在寻找的,感谢更新。
    • 谢谢。这太棒了。
    【解决方案3】:

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

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

    【讨论】:

    • 致读者:现在支持out of the box
    • 这个答案现在已经很老了:)
    • 我理解,不是批评?
    • 也许我们应该能够将这些页面标记为过时的、仍然可见的,但它需要看起来与普通问题完全不同,例如顶部的大警告之类的。对旧问题的所有这些旧答案都出现令人困惑,C# 在 10 年内发生了一些变化。
    【解决方案4】:

    让我们以另一种方式看待这个问题。如果你有:

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

    然后您可以改用查找表,它的执行速度与switch 语句相似,但效率不高(因为它需要计算哈希)。不过,它可能已经足够好了。它让您有机会命名案例,让这种组合爆炸稍微不那么令人困惑和难以维护。

    代码示例:

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

    如果您需要为每种情况实际执行一个函数或方法,那么您可以使用Action&lt;T&gt;Func&lt;T&gt; 的结果类型(字典值)。

    请注意,我在这里使用Tuple&lt;T1,T2,T3&gt;,因为它已经内置了所有哈希码逻辑。C# 中的语法有点笨拙,但如果您愿意,您可以实现自己的查找类并覆盖 @ 987654329@和GetHashCode

    【讨论】:

    • +1。你选择了Tuple,我选择了单独的查找类。我想我更喜欢你的想法。
    【解决方案5】:

    我对此非常疯狂:

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

    【讨论】:

    • 有趣的准功能版本,虽然不是类型安全的(并且可能不是比较安全的)。我可能会使用IComparableIEquatable 而不仅仅是object。更好的版本会使用泛型。
    【解决方案6】:

    你可以转换成字符串:

    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 的额外属性,它可以是枚举或字符串,您可以使用它来指定某人所在的组。

    【讨论】:

      【解决方案7】:

      2018 年更新。从 C#7.0 开始,Microsoft 引入了用于切换的“when”子句,从而可以有效地扩展具有附加条件的切换案例。

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

      【讨论】:

        【解决方案8】:

        我不确定这是哪个 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;
        }
        

        【讨论】:

          【解决方案9】:

          根据 C# 语言规范,switch 语句表达式必须解析为 sbyte, byte, sbyte, byte, short, ushort, int, uint, long, ulong, char, string, or an enum-type 之一。这意味着您无法打开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;
          }
          

          【讨论】:

          • 我把它看作是一个谜题。
          • 如果你为这些值创建一个枚举,这是非常可读的。另外,当我读完这个问题后,我脑海中的第一个想法就是使用按位语句和标志。
          • 我也考虑过这种可能性,但不是很好:(
          • 尤其是当您将问题更改为现在包含一个字符串作为第二个“参数”时,它最初是一个 int。
          • 如果都是枚举,您可以使用+ 以更易读的方式编写case,因为它仍将在编译时进行评估。例如。对于枚举 DayMonthDay 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; ...}
          【解决方案10】:

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

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

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

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

          【讨论】:

          • 嗨,是的,我可以,但是这个开关只需要一个变量,我想处理三个。
          • @BanditoBunny - 那么答案是否定的,你不能那样做。
          • -1:正确答案,您的开关除外。删除它,我将删除反对票。
          • 错误!= 可能 - 你想要的东西是不可能的,所以我的解决方案没有错 - 它只是不适合你的组合爆炸:)(说得好听)。
          • @JonH - 这甚至没有达到作者想要的效果。当然,他想要的既不能按照语言的规范来做,也不应该做。这做了一些完全不同的事情,Raymond Chen 看起来很糟糕的代码,接近作者想要的。
          【解决方案11】:
          if (a == 1 && b == 1) {}
          else if (a == 1 && b == 2) {}
          else if (a == 2 && b ==2) {}
          

          【讨论】:

            【解决方案12】:

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

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

            需要设置一些代码,但执行起来非常快。

            【讨论】:

              【解决方案13】:
              //.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();
                          }
                      }
                  }
              

              【讨论】:

                猜你喜欢
                • 2011-02-01
                • 1970-01-01
                • 1970-01-01
                • 2019-03-02
                • 1970-01-01
                • 2014-11-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多