如何在 switch 语句中使用 C# 的元组值类型

23

我正在使用 .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 语句针对元组进行分支处理?


您不能使用元组作为 switch 的值,switch 只接受常量值。 - Gusman
@Gusman 不仅如此,实际上。 - Yeldar Kurmangaliyev
@YeldarKurmangaliyev 如果你指的是使用类型的新语法,那么这些类型也可以被视为常量。 - Gusman
1
@Gusman 我的意思是 when 语法。case Rectangle r when r.Height == r.Width 不是很恒定 :) - Yeldar Kurmangaliyev
7个回答

24

只是提醒一下那些偶然发现这个问题的人。

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

现在您可以像这样操作:

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中。

1
如果您遇到以下错误:只有赋值、调用、增量、减量和新对象表达式可以用作语句,则可以修改上面的代码为 `var test = ('A', 'B') switch { ('A', 'B') => "OK", ('A', ) => "第一部分 OK", (, 'B') => "第二部分 OK", _ => "不 OK", }; Console.WriteLine(test);` - Nadeem Yousuf-AIS
谢谢,@NadeemYousuf-AIS。我还没有尝试过它。已编辑上面的答案。 - anuith

19

从技术上回答您的问题,您可以使用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字符的含义,这违反了单一职责原则。
从面向对象编程角度来看,最好将这些知识从你的主要代码中分离出来,放在一个独立的方法中。
像这样的做法能使代码变得更加简洁:

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属性并在其内部封装此逻辑的类。

如果这些字母在您的领域中有任何含义,则最好创建一个具有两个char属性并在其内部封装此逻辑的类。


7
模式匹配是函数式编程的概念,而不是面向对象编程的概念。根据值进行匹配完全没有问题,也不会违反单一职责原则。请查看Match Expression中的“绑定到值”部分。这样做实际上更容易分离职责。将switch语句分成两部分后,单一职责原则变成了两个不同的代码片段。 - Panagiotis Kanavos

16
C# 7.3引入了元组相等性,这意味着你在问题中的初始想法几乎是正确的。你只需要像这样捕获你要比较的值:
var _test = ('A','B');
switch (_test)
{
   case var t when t == ('A', 'B'):
   Console.WriteLine("Case ok.");
   break;
}

1
如果比较标记为错误,则按alt+enter /“升级到C# 7.3”。 - Istvan Heckl

6

使用元组或模式匹配没有问题。事实上,这些方法可以让你编写更简洁的代码,避免将逻辑分散到多个方法中。

目前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
如果元组中一个是可行的但两个不可行,那么您可以使用下划线符号_来舍去其中一个。
switch (_test)
{
    case ('A', 'B'):
        Console.WriteLine("Case A B ok.");
        break;
    case ('C', 'D'):
        Console.WriteLine("Case C D ok.");
        break;
    case ('A', _):
        Console.WriteLine("Case A ok.");
        break;
    case (_, 'B'):
        Console.WriteLine("Case B ok.");
        break;
    default:
        Console.WriteLine("Nothing ok.");
        break;
}

更新 - C# 8.0

您可以使用switch表达式来处理元组模式

Console.WriteLine(_test switch
{
    ('A', 'B') => "Case A B ok.",
    ('C', 'D') => "Case C D ok.",
    ('A', _)   => "Case A ok.",
    (_, 'B')   => "Case A ok.",
    _          => "Nothing ok."
});

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语句的方法,请随时举例说明。

1
你不需要放弃元组,可以使用_test.Equals(('A', 'B')) - Panagiotis Kanavos
看起来你正在尝试为解析器实现状态机?你可以使用模式匹配来实现,但不能使用元组。 - Panagiotis Kanavos

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接