switch 语句把一个值与一组常量 case 标签进行比较,并执行匹配的那一段。在 C# 中,每一段都必须以 break(或其他能离开该段的语句)结束,所以一个 case 永远不会悄悄地执行到下一个 case。
输出:
200: OK
302: redirect
404: not found
500: unhandled status
括号里的值只计算一次,然后与每个标签比较。没有任何匹配时执行 default;它是可选的,可以出现在列表中的任何位置,不过通常放在最后。如果没有匹配也没有 default,整个 switch 什么也不做。
每一段都需要 break
在 C、C++、Java 和 JavaScript 中,忘记写 break 会让执行滑入下一个 case。C# 把这个错误变成了编译错误:
switch (level)
{
case 1:
Console.WriteLine("read");
// error CS0163: Control cannot fall through from one case label ('case 1:') to another
case 2:
Console.WriteLine("write");
break;
}
规则是:一段语句列表的末尾不能是可达的。break 是满足这条规则的常用方式,但任何能离开该段的语句都可以:
return离开整个方法,在小型辅助方法中这往往是最简洁的写法。throw针对永远不该出现的值抛出异常。- 当
switch位于循环内时,continue跳到下一次迭代。 goto case和goto default有意跳到另一段。
最后一段也不例外。底部的 default: 如果没有 break,会报错 CS8070 Control cannot fall out of switch from final case label ('default:')。
一个 case 匹配多个值
中间没有语句的多个标签共享它们下面的那一段。这是 C# 唯一允许的贯穿,因为空标签里藏不住任何代码:
输出:
'O' is a vowel
'k' is a consonant or symbol
' ' is a space
'7' is a digit
这里每一段都以 return 结束,所以不需要 break(return 后面的 break 会是无法访问的代码)。两个值相同的标签会报错 CS0152 The switch statement contains multiple cases with the label value '1',而且每个标签都必须是编译时常量:在 case 中使用变量会报错“应输入常量值”。
对字符串进行 switch
除了整数,字符串是最常用来 switch 的值。比较按序号进行并区分大小写,所以在 switch 之前先规范化输入:
输出:
Starting the server
Stopping the server
Server is running
Unknown command: restart
Unknown command: (none)
如果没有 ToLowerInvariant,"STOP" 就会进入 default。对 null 值进行 switch 是合法的:它不匹配任何字符串常量,会落到 default,所以 switch 本身永远不会抛出异常。
对枚举进行 switch
枚举正是 switch 的用武之地:一组封闭的具名值。
输出:
Pending: wait for payment
Paid: pack and ship
Shipped: track the parcel
Delivered: nothing, order is closed
Cancelled: nothing, order is closed
ArgumentOutOfRangeException for value 42
编译器不会检查 switch 语句是否覆盖了每个枚举成员,而且枚举变量可以保存其底层类型的任意整数((OrderStatus)42 是合法的)。会 抛出异常 的 default 能把遗漏的 case 变成立即、明显的失败,而不是悄无声息地什么都不做。
goto case 和 goto default
当一个 case 确实应该继续执行另一个 case 时,C# 要求你把跳转写出来:
输出:
admin:
manage users
edit posts
read posts
editor:
edit posts
read posts
guest:
read posts
goto case 接受一个常量,它必须与同一个 switch 中的某个标签匹配。它在实际代码中很少见:共享的行为通常写成一个供多个 case 调用的方法会更清晰。但它仍然是“C# 中如何贯穿”这个问题的答案。
case 中的变量
整个 switch 块是一个作用域,所以两个 case 不能声明同名的变量:
switch (shape)
{
case "square":
int area = side * side;
break;
case "rectangle":
int area = width * height; // error CS0128: A local variable or function named 'area' is already defined in this scope
break;
}
把每一段的主体用花括号包起来,就能给它一个独立的作用域:case "square": { int area = side * side; ... break; }。
循环中的 break
case 中的 break 离开的是 switch,而不是外面的循环。continue 正好相反:switch 不是循环,所以 continue 会进入循环的下一次迭代。
输出:
end marker found
Total: 134
循环在 "end" 之后继续执行,并加上了 99。要在 switch 内部停止循环,可以设置一个标志并在 switch 之后检查它,或者把循环移到一个方法里再用 return。break 和 continue 页面对这些做法进行了比较。
模式 case 标签与 when
从 C# 7.0 开始,case 标签可以是模式而不只是常量:它可以检查值的类型,还可以加上 when 条件。这种形式在现代 C# 代码中很常见:
static string Describe(object value)
{
switch (value)
{
case null:
return "nothing";
case int n when n < 0:
return $"negative int {n}";
case int n:
return $"int {n}";
case string s when s.Length == 0:
return "empty string";
case string s:
return $"string \"{s}\"";
default:
return $"other: {value.GetType().Name}";
}
}
使用模式时,顺序很重要:第一个匹配的 case 胜出,而且如果某个 case 因为前面的 case 已经覆盖了它而永远无法到达,编译器会报错(把 case int n: 放在 case int n when n < 0: 上面就会失败)。when 子句也适用于常量 case:case 0 when isAdmin:。C# 9 新增了关系模式和逻辑模式,所以 case 可以写成 case >= 90: 或 case 'a' or 'e' or 'i':。
在没有模式的 C# 7 中,同样的类型分派要写成使用 is 类型模式的一串 if:
输出:
negative int -3
int 12
empty string
string "hi"
other: Decimal
nothing
当 switch 的每个分支都只是计算一个值时,switch 表达式还要更短:var grade = score switch { >= 90 => "A", >= 80 => "B", _ => "C" };。switch 表达式页面介绍了它的语法、模式和穷尽性规则。
可以对哪些类型进行 switch
| 值的类型 | 常量 case | 说明 |
|---|---|---|
int、long、byte 及其他整数 | 支持 | 最经典的用法 |
char | 支持 | case 'a': |
string | 支持 | 按序号、区分大小写,null 进入 default |
bool | 支持 | 合法,但用 if 更清晰 |
任意 enum | 支持 | 不检查是否穷尽 |
double、decimal | 从 C# 7 起 | 只能用常量;范围需要 C# 9 的关系模式 |
| 任意其他类型 | 从 C# 7 起 | 通过类型模式,case Circle c: |
常见问题
C# switch 中必须写 break 吗?
每个 case 段都必须以一条离开该段的语句结束:通常是 break,但 return、throw、continue(在循环内)或 goto case 也可以。从一个段的末尾落入下一个段是编译错误 CS0163 Control cannot fall through from one case label ('case 1:') to another,最后一个段也不例外。
C# switch 中如何让一个 case 处理多个值?
把标签叠在一起,中间不写任何内容:case "sat": case "sun": Console.WriteLine("weekend"); break;。空的 case 标签可以贯穿到下一个标签;只有包含语句的段才必须以 break 结束。在 C# 9 及更高版本中,模式 case 还可以写成 case 1 or 2 or 3:。
C# 中对字符串 switch 区分大小写吗?
区分。字符串 case 按序号、区分大小写的方式比较是否相等,所以 "Yes" 不会匹配 case "yes":。先用 switch (input.Trim().ToLowerInvariant()) 规范化值。null 字符串不匹配任何常量 case,会进入 default。
C# 的 switch 能像 C 或 Java 那样贯穿吗?
不能隐式贯穿。当你希望一个 case 继续执行另一个 case 时,要用 goto case <value>; 或 goto default; 明确写出来。这样每一次贯穿在代码里都清晰可见,这也是 C# 禁止隐式贯穿的原因。
C# 中 switch 语句和 switch 表达式有什么区别?
switch 语句执行语句,需要 case、break 和 default。switch 表达式(C# 8 及更高版本)产生一个值:var label = n switch { 0 => "zero", > 0 => "positive", _ => "negative" };。当每个分支都只是计算一个结果时,用表达式。