【问题标题】:How to use c# tuple value types in a switch statement如何在switch语句中使用c#元组值类型
【发布时间】:2017-11-05 10:35:21
【问题描述】:

我在 .net 4.7 中使用新的元组值类型。在这个例子中,我试图为一个或多个元组的情况创建一个 switch 语句:

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

        }
    }
}

不幸的是,这不能编译。

如何在 switch 语句中正确获取元组和大小写?

【问题讨论】:

  • 不能使用元组作为开关值,开关只接受常量值。
  • @Gusman 实际上不仅如此。
  • @YeldarKurmangaliyev 如果你的意思是新的使用类型的sintax,类型也可以被认为是常量。
  • @Gusman 我的意思是when 语法。 case Rectangle r when r.Height == r.Width 不是很固定 :)

标签: c# switch-statement tuples c#-7.0


【解决方案1】:

只是给偶然发现这个问题的人的注释。

C# 8.0 引入了switch expressions,这在这种情况下非常有用。

现在你可以这样做了:

var test = ('A', 'B');
var result = test switch
{
    ('A', 'B') => "OK",
    ('A',   _) => "First part OK",
    (  _, 'B') => "Second part OK",
    _ => "Not OK",
};

Console.WriteLine(result);

试试.NET fiddle

【讨论】:

  • 万一报错:只能用赋值、调用、自增、自减、新对象表达式作为语句,以上代码修改为var test = ('A', 'B') switch { ('A', 'B') => "OK", ('A', _) => "First part OK", (_, 'B') => "Second part OK", _ => "Not OK", }; Console.WriteLine(test);
  • 谢谢,@NadeemYousuf-AIS 我没试过。编辑了上面的答案。
【解决方案2】:

C# 7.3 引入了元组相等,这意味着您在问题中的最初想法几乎是正确的。您只需要像这样捕获要比较的值:

var _test = ('A','B');
switch (_test)
{
   case var t when t == ('A', 'B'):
   Console.WriteLine("Case ok.");
   break;
}

【讨论】:

  • 如果比较被标记为错误,则 alt+enter / "uprade to c# 7.3"
【解决方案3】:

case (...): 的语法是为将来的模式保留的。请参阅 C# 语言功能规范中描述的位置模式:https://github.com/dotnet/csharplang/blob/master/proposals/patterns.md#positional-pattern

【讨论】:

    【解决方案4】:

    使用元组或模式匹配没有任何问题。如果有的话,这些允许您编写更清晰的代码并避免将您的逻辑扩展到多个方法。

    C# 7 不允许您匹配元组值。您也不能使用 == 运算符比较两个元组。你可以做的是使用Equals两个比较两个值元组:

     if (_test.Equals(('A','B'))
    {
        Console.WriteLine("Case A ok.");
    }
    else if (_test.Equals(('D','\0'))
    {
        Console.WriteLine("Case D ok.");
    }
    

    您似乎正在尝试为解析器(?)创建一个匹配特定模式的状态匹配项。如果您指定不同的状态类而不是对所有情况使用单个元组,则此可以与模式匹配一​​起使用。

    您需要做的就是指定一个不带方法的 IState 接口,并在所有状态类中使用它,例如:

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

    ad 变量是强类型的,这意味着您不必向IState 接口添加任何内容。它只是为了满足编译器。

    通过对状态类型使用结构而不是类,您将获得与使用元组相同的内存优势。如果要使用解构,可以为每个类型添加Deconstruct方法,或者在单独的静态类中使用Deconstruct扩展方法。

    【讨论】:

      【解决方案5】:

      从技术上回答您的问题,您可以使用when 检查元组的值:

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

      但是,请考虑使用if 版本,因为它可能是一个更易读和易于理解的解决方案。

      这个问题的另一方面是单一责任。您的方法知道ABD\0 字符的含义是什么,这违反了单一职责原则。
      在 OOP 方面,最好将这些知识从您的主代码中分离到一个单独的方法中。
      这样的东西可以使代码更简洁:

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

      如果这些字母在您的领域中具有任何意义,那么最好甚至创建一个具有两个 char 属性的类并将此逻辑封装在那里。

      【讨论】:

      • 模式匹配是一个功能性的,而不是一个 OOP 概念。匹配一个值是完全可以的,并且不会破坏 SRP。检查Match Expression 中的Binding to Values 部分。如果有的话,这可以更容易地分离职责。通过将switch 分成两部分,Single Responsibility 变成了两段不同的代码
      【解决方案6】:

      感谢您的回复。

      我决定放弃使用 switch 语句并使用旧的 if/else 语句。

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

      这样我可以决定是否要测试元组中的所有值并按照我需要的顺序。我认为在性能上应该不会有太大的不同。

      如果有另一种使用带有 switch 语句的元组的方法,请随意举个例子。

      【讨论】:

      • 你不需要放弃元组,使用_test.Equals(('A', 'B'))
      • 看起来您正在尝试为解析器实现状态机?您可以为此使用模式匹配,但不能使用元组
      猜你喜欢
      • 1970-01-01
      • 2011-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-10
      • 1970-01-01
      • 2016-07-12
      • 1970-01-01
      相关资源
      最近更新 更多