如何在switch语句中使用c#tuple值类型

时间:2017-06-04 15:20:43

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

我在.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语句中创建案例?

6 个答案:

答案 0 :(得分:8)

从技术上回答您的问题,您可以使用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属性的类并在那里封装该逻辑。

答案 1 :(得分:6)

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

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

答案 2 :(得分:5)

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

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扩展方法。

答案 3 :(得分:2)

感谢您的回复。

我决定放弃使用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语句的元组,请随意举例。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

为任何偶然发现此问题的人提供一个便条。

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

现在您可以执行以下操作:

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