C# switch 语句与表达式详解
在 C# 编程中,控制流语句是构建程序逻辑的基石。它们决定了程序在不同条件下执行哪些代码块。在众多控制流语句中,switch 语句/表达式是一种功能强大且灵活的工具,用于根据一个表达式的值来执行不同的代码路径。
随着 C# 语言的不断发展,switch 的功能也在逐步增强,从最初的简单值匹配,演变为支持复杂的模式匹配,并引入了更简洁的 switch 表达式语法。本文将深入探讨 C# 中 switch 的方方面面,包括其传统用法、现代特性、模式匹配能力、使用场景以及最佳实践。
第一章:传统的 switch 语句
在 C# 1.0 中引入的传统 switch 语句,提供了一种替代冗长 if-else if 链的方式,特别适用于基于单一表达式的精确值匹配。
1.1 基本语法和结构
传统的 switch 语句的基本语法如下:
csharp
switch (expression)
{
case value1:
// 当 expression 的值等于 value1 时执行的代码
break; // 或 goto case, goto default, return, throw
case value2:
// 当 expression 的值等于 value2 时执行的代码
// 可以有多个语句
break;
// ... 其他 case
case valueN:
// 当 expression 的值等于 valueN 时执行的代码
break;
default:
// 当 expression 的值不匹配任何 case 时执行的代码
// default 是可选的,但推荐使用
break;
}
组成部分解析:
switch (expression):switch关键字后跟着一对圆括号,圆括号内是要进行匹配的表达式。这个表达式的值将用于与各个case的值进行比较。case value::case关键字后跟着一个常量值或常量表达式,以及一个冒号。当switch表达式的值等于这个value时,与此case关联的代码块将被执行。case的值必须是编译时已知的常量。- 代码块:
case后的代码块包含一个或多个语句。 - 控制流语句: 每个非空的
case代码块(包括default)必须以某个控制流语句结束,例如break、goto case、goto default、return或throw。这是 C# 的一个重要特性,与 C++ 或 Java 等语言不同,C# 不允许隐式的“fall-through”(穿透),即代码执行完一个case后不会自动进入下一个case。 default::default关键字标记了当switch表达式的值与所有case值都不匹配时执行的代码块。default是可选的,且可以放在switch块内的任何位置(尽管习惯上放在最后)。
1.2 break 关键字的重要性
如前所述,C# 的 switch 不允许隐式穿透。这意味着,如果你在一个 case 块中写了一些代码,你必须明确告诉编译器执行完这些代码后应该做什么。最常见的方式就是使用 break;。
break; 语句的作用是立即退出当前的 switch 块,程序将继续执行 switch 语句之后的代码。
为什么禁止隐式穿透?
隐式穿透在某些语言中可以用来处理多个 case 执行相同逻辑的情况,但这同时也带来了可读性差、容易出错的问题。C# 的设计者认为强制使用 break(或其它控制流语句)可以提高代码的清晰度和健壮性。
如果你确实需要让多个 case 执行相同的逻辑,可以将多个 case 标签写在一起,不包含任何代码,然后只在最后一个 case 中写代码和 break:
“`csharp
int dayOfWeek = (int)DateTime.Now.DayOfWeek;
switch (dayOfWeek)
{
case 0: // Sunday
case 6: // Saturday
Console.WriteLine(“It’s the weekend!”);
break;
case 1: // Monday
case 2: // Tuesday
case 3: // Wednesday
case 4: // Thursday
case 5: // Friday
Console.WriteLine(“It’s a weekday.”);
break;
default:
Console.WriteLine(“Invalid day.”);
break;
}
“`
1.3 default 标签
default 标签是可选的,用于处理 switch 表达式值不匹配任何 case 值的情况。如果没有 default 标签,且 switch 表达式的值不匹配任何 case,则 switch 块内的任何代码都不会被执行,程序会跳过整个 switch 块。
推荐在大多数情况下使用 default,它可以作为一种捕获未预期值的安全网,或者用于处理正常但非特定 case 的情况。
1.4 goto case 和 goto default
尽管不常见,C# 也提供了 goto case value; 和 goto default; 语句,允许程序从一个 case 代码块跳转到另一个 case 代码块或 default 代码块。这模仿了某些语言中的 fall-through 行为,但更加显式。
使用 goto case 或 goto default 可能会使代码难以阅读和维护,因此应谨慎使用。它们通常用于处理某些前置条件或共享清理逻辑的场景。
“`csharp
int score = 75;
char grade;
switch (score / 10)
{
case 10:
case 9:
grade = ‘A’;
break;
case 8:
grade = ‘B’;
break;
case 7:
grade = ‘C’;
break;
case 6:
grade = ‘D’;
break;
default:
grade = ‘F’;
break; // Default case needs a break too, or other control flow
}
Console.WriteLine($”Score: {score}, Grade: {grade}”);
// 另一种(不推荐滥用)使用 goto case 的例子,展示显式跳转
int state = 1;
switch (state)
{
case 1:
Console.WriteLine(“State 1”);
// 执行一些 State 1 特有的操作
goto case 2; // 显式跳转到 State 2
case 2:
Console.WriteLine(“State 2”);
// 执行一些 State 2 特有的操作,可能也包括 State 1 需要的公共操作
goto case 3; // 显式跳转到 State 3
case 3:
Console.WriteLine(“State 3”);
// 执行 State 3 的操作
break; // 结束 switch
default:
Console.WriteLine(“Unknown state”);
break;
}
``goto case
在这个的例子中,如果state是 1,程序会依次输出 "State 1", "State 2", "State 3"。这模拟了传统的 fall-through,但需要明确写出goto` 语句。
1.5 支持的数据类型
传统的 switch 语句的 expression 和 case 值支持以下数据类型:
- 整数类型:
sbyte,byte,short,ushort,int,uint,long,ulong - 字符类型:
char - 布尔类型:
bool(尽管用if/else处理布尔值更常见) - 枚举类型:
enum - 字符串类型:
string - 上述值类型的 Nullable 版本(
int?,bool?等)。
1.6 传统 switch 的局限性
尽管传统 switch 语句对于精确的值匹配非常有效,但它存在一些局限性:
- 只能进行相等比较:
case只能匹配表达式的 精确 值,无法进行范围比较(如> 10)或类型比较。 - 只基于一个表达式: 无法基于多个条件或表达式的组合进行分支。
- 语法相对冗长: 对于简单的值到结果的映射,需要写
switch (...) { case ...: ... break; }的结构,不如函数调用或查找表简洁。 - 强制
break: 虽然提高了安全性,但在某些需要模拟 fall-through 的场景下,goto case显得不够优雅。
正是为了解决这些局限性,C# 在后续版本中引入了 switch 表达式和模式匹配。
1.7 传统 switch 示例
“`csharp
// 示例 1: 处理颜色枚举
enum Color { Red, Green, Blue, Yellow }
Color myColor = Color.Green;
string colorDescription;
switch (myColor)
{
case Color.Red:
colorDescription = “Primary color, associated with passion.”;
break;
case Color.Green:
colorDescription = “Secondary color, associated with nature and growth.”;
break;
case Color.Blue:
colorDescription = “Primary color, associated with calmness and stability.”;
break;
case Color.Yellow:
colorDescription = “Secondary color, associated with happiness and energy.”;
break;
default:
colorDescription = “Unknown color.”;
break;
}
Console.WriteLine($”Color: {myColor}, Description: {colorDescription}”);
// 示例 2: 处理用户输入字符串
Console.Write(“Enter command (start, stop, pause): “);
string command = Console.ReadLine();
switch (command.ToLower()) // 通常对字符串输入进行规范化处理
{
case “start”:
Console.WriteLine(“Starting service…”);
// 调用启动逻辑
break;
case “stop”:
Console.WriteLine(“Stopping service…”);
// 调用停止逻辑
break;
case “pause”:
Console.WriteLine(“Pausing service…”);
// 调用暂停逻辑
break;
default:
Console.WriteLine(“Unknown command.”);
break;
}
“`
第二章:C# 中的 switch 表达式 (C# 8.0+)
为了提供更简洁、更具函数式风格的方式来根据输入生成输出,C# 8.0 引入了 switch 表达式。switch 表达式是一种表达式,意味着它可以产生一个值,可以直接用于赋值或作为参数。
2.1 引入动机
传统的 switch 语句是一个语句,主要用于执行不同的操作或修改状态。当你的目标是根据输入值计算并返回一个值时,传统的 switch 语句通常需要先声明一个变量,然后在每个 case 中给变量赋值,最后在 switch 外部使用这个变量。这种模式在函数式编程风格中显得有些笨重。
switch 表达式的设计目的就是为了简化这种“值到值”的映射场景。
2.2 switch 表达式语法
switch 表达式的语法与传统 switch 语句有显著区别:
csharp
var result = expression switch
{
value1 => result1, // 或一个表达式
value2 => result2,
// ... 其他 case
valueN => resultN,
_ => defaultValue // discard pattern 作为 default
};
组成部分解析:
expression switch: 要匹配的表达式写在switch关键字之前。{ ... }:case和default逻辑写在一对花括号内。pattern => result: 每个分支由一个 模式 (pattern)、一个 Lambda 箭头 (=>) 和一个结果表达式 (result) 组成。当expression匹配pattern时,result表达式会被评估,其值成为整个switch表达式的结果。,分隔符: 每个分支之间使用逗号,分隔。- 没有
break:switch表达式没有break关键字,因为它是一个表达式,每个分支执行完毕后自然就产生了结果并退出。 - 丢弃模式 (
_) 作为default:switch表达式中没有default关键字。取而代之的是使用丢弃模式 (_) 作为最后一个模式。丢弃模式匹配任何值,因此它充当了传统switch语句中default的角色,用于处理所有未被前面模式匹配到的情况。丢弃模式分支通常放在最后。 - 必须穷尽所有可能: 与传统
switch语句不同,switch表达式通常要求是“穷尽的”(exhaustive),即必须覆盖所有可能的输入值。如果输入的类型是枚举或者 sealed class/struct,编译器会检查是否覆盖了所有成员。对于其他类型,通常需要一个_模式来确保穷尽性。
2.3 返回值
switch 表达式的核心就是它会产生一个值。每个分支的 => 后面的部分必须是一个表达式,这个表达式的类型必须与整个 switch 表达式期望的返回类型兼容(或者编译器能够推断出统一的类型)。
“`csharp
// 示例: 将颜色枚举转换为字符串描述
enum Color { Red, Green, Blue, Yellow, Orange }
Color myColor = Color.Orange;
string colorDescription = myColor switch
{
Color.Red => “Primary color: Red”,
Color.Green => “Secondary color: Green”,
Color.Blue => “Primary color: Blue”,
Color.Yellow => “Secondary color: Yellow”,
_ => “Unknown or tertiary color” // 丢弃模式处理所有其他情况
};
Console.WriteLine($”Color: {myColor}, Description: {colorDescription}”); // Output: Color: Orange, Description: Unknown or tertiary color
“`
这个例子比使用传统 switch 语句给变量赋值要简洁得多。
第三章:switch 语句与表达式中的模式匹配 (C# 7.0+ 逐步增强)
C# 7.0 引入了模式匹配的概念,并将其集成到 is 运算符和 switch 语句中。随后的 C# 版本(8.0, 9.0, 10.0)进一步增强了模式匹配的能力,特别是与 switch 表达式的结合,使其成为处理复杂条件分支的强大工具。
模式匹配允许你不仅仅基于一个表达式的 精确 值进行匹配,还可以基于其 类型、属性、元素、相对值 或 逻辑组合 进行匹配。
3.1 模式的种类
C# 支持多种模式,可以在 switch 的 case 标签(对于语句)或分支(对于表达式)中使用:
-
常量模式 (Constant Pattern): 匹配表达式的值是否等于某个常量。这是传统
switch就支持的模式。- 语法:
value(例如case 1:,case "hello":,Color.Red => ...)
- 语法:
-
类型模式 (Type Pattern): 匹配表达式的运行时类型是否兼容指定的类型。如果是,还可以将表达式转换为该类型并声明一个局部变量供后续使用。
- 语法:
Type variableName(例如case int i:,string s => ...)
- 语法:
-
var模式 (varPattern): 总是匹配成功,并将表达式的值赋给一个新声明的局部变量,该变量的类型与表达式的类型相同。通常用于在switch表达式中捕获输入值,或与when子句结合使用。- 语法:
var variableName(例如case var o:,var x => ...) – 注意在 switch 语句中,var 模式后面通常需要when子句才有意义,否则它会匹配所有非 null 值。
- 语法:
-
属性模式 (Property Pattern) (C# 8.0+): 匹配表达式的属性是否满足指定的模式。可以嵌套使用。
- 语法:
{ Property1: pattern1, Property2: pattern2, ... }(例如{ X: 0, Y: 0 },{ Address: { City: "London" } })
- 语法:
-
位置模式 (Positional Pattern) (C# 8.0+): 匹配表达式的去构造函数 (Deconstruct method) 返回的值是否满足指定的模式。主要用于
record类型或提供了Deconstruct方法的类型。- 语法:
(pattern1, pattern2, ...)(例如(0, 0),("John", _))
- 语法:
-
关系模式 (Relational Pattern) (C# 9.0+): 匹配表达式的值是否满足一个关系运算符 (
<,>,<=,>=) 和一个常量。- 语法:
< constant,> constant,<= constant,>= constant(例如case > 10:,< 0 => ...)
- 语法:
-
逻辑模式 (Logical Pattern) (C# 9.0+): 使用
and、or和not逻辑运算符组合其他模式。- 语法:
pattern1 and pattern2,pattern1 or pattern2,not pattern(例如case int i and > 10:,(not null) => ...)
- 语法:
-
括号模式 (Parenthesized Pattern): 使用括号
()明确模式的优先级。- 语法:
(pattern)
- 语法:
-
丢弃模式 (Discard Pattern) (C# 8.0+): 用下划线
_表示,匹配任何值(包括null)。在switch表达式中用作default。在switch语句中,如果用作case _:,它会匹配所有非 null 值,因此通常用default更好。丢弃模式通常用于表示“我不在乎这个值”。
3.2 when 子句 (Guard Clause)
模式匹配还支持 when 子句,它是一个额外的布尔条件,必须在模式匹配成功后才会评估。只有当模式和 when 条件都满足时,对应的分支才会被选中。
- 语法:
pattern when boolean_condition(例如case int i when i > 0:,{ Status: "Active" } when user.IsInRole("Admin") => ...)
when 子句使得 switch 能够处理更复杂的逻辑,这些逻辑不能直接通过简单的模式表达。
3.3 switch 语句中的模式匹配示例 (C# 7.0+)
“`csharp
object input = “hello”; // 或者 123,或者 null, 或者 new Person { Name = “Alice” }
switch (input)
{
case int i: // 类型模式:匹配 int 类型,并将值赋给变量 i
Console.WriteLine($”Input is an integer: {i}”);
break;
case string s: // 类型模式:匹配 string 类型,并将值赋给变量 s
Console.WriteLine($”Input is a string: {s.Length} characters long”);
break;
case Person p when p.Name == “Alice”: // 类型模式 + when 子句:匹配 Person 类型且名字是 Alice
Console.WriteLine($”Input is Alice the Person.”);
break;
case Person p: // 类型模式:匹配任何 Person 类型 (顺序很重要!)
Console.WriteLine($”Input is a generic Person named {p.Name}”);
break;
case null: // 常量模式:匹配 null
Console.WriteLine(“Input is null.”);
break;
case : // 丢弃模式:匹配任何前面未匹配到的值 (C# 7.0 中 _ 在 switch 语句中通常匹配非 null 值,default 更好)
// 在 C# 8.0+ 中, 在 switch 语句中行为有点复杂,通常还是用 default 明确表示其他所有情况
default: // default 标签:匹配任何前面未匹配到的值 (包括 null, 如果前面的 case null 没有捕获)
Console.WriteLine($”Input is of unknown type: {input?.GetType().Name ?? “null”}”);
break;
}
class Person { public string Name { get; set; } }
``case Person p when p.Name == “Alice”:
**注意和case Person p:的顺序。** 在传统的switch语句中,case的顺序通常不影响匹配结果(因为是精确值匹配)。但在使用模式匹配时,特别是类型模式或更复杂的模式,case的顺序变得**非常重要**。编译器会按照从上到下的顺序评估模式,一旦找到第一个匹配项,就会执行相应的代码块并退出switch(如果使用了break)。因此,更具体的模式(如Person p when …)应该放在更通用的模式(如Person p`)之前。
3.4 switch 表达式中的模式匹配示例 (C# 8.0+)
模式匹配在 switch 表达式中尤为强大和常用,因为它可以简洁地表达基于复杂条件的映射关系。
“`csharp
// 示例 1: 类型模式和常量模式
object obj = 123;
string typeDescription = obj switch
{
int i => $”Integer with value {i}”,
string s => $”String with length {s.Length}”,
null => “Null value”, // 常量模式匹配 null
_ => “Something else” // 丢弃模式作为 default
};
Console.WriteLine(typeDescription);
// 示例 2: 属性模式
// 假设有一个 Point 类
public class Point
{
public int X { get; init; }
public int Y { get; init; }
}
Point p = new Point { X = 5, Y = 5 };
string locationDescription = p switch
{
{ X: 0, Y: 0 } => “Origin”, // 属性模式:匹配 X 和 Y 都为 0 的点
{ X: var x, Y: var y } when x == y => $”On the diagonal (x=y={p.X})”, // 属性模式 + var 模式 + when 子句
{ X: 0 } => “On the Y axis”, // 属性模式:匹配 X 为 0 的点
{ Y: 0 } => “On the X axis”, // 属性模式:匹配 Y 为 0 的点
_ => “Elsewhere” // 丢弃模式
};
Console.WriteLine($”Point ({p.X}, {p.Y}) is: {locationDescription}”);
// 示例 3: 位置模式 (假设 Point 有 Deconstruct 方法)
/
public class Point
{
public int X { get; init; }
public int Y { get; init; }
public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}
/
// 使用上面 Point 类,假设 Deconstruct 方法存在
Point p2 = new Point { X = 10, Y = -5 };
string locationDescription2 = p2 switch
{
(0, 0) => “Origin”, // 位置模式:匹配 Deconstruct 返回 (0, 0)
(var x, var y) when x > 0 && y > 0 => “In Quadrant 1”, // 位置模式 + var 模式 + when 子句
(var x, var y) when x < 0 && y > 0 => “In Quadrant 2”,
(var x, var y) when x < 0 && y < 0 => “In Quadrant 3”,
(var x, var y) when x > 0 && y < 0 => “In Quadrant 4”,
(, 0) => “On X axis”, // 位置模式:匹配 Deconstruct 返回 (, 0) – Y 为 0,X 不关心
(0, ) => “On Y axis”, // 位置模式:匹配 Deconstruct 返回 (0, ) – X 为 0,Y 不关心
_ => “Somewhere else”
};
Console.WriteLine($”Point ({p2.X}, {p2.Y}) is: {locationDescription2}”);
// 示例 4: 关系模式 (C# 9.0+)
int temperature = 15;
string weatherSuggestion = temperature switch
{
< 0 => “Freezing, wear heavy layers.”,
>= 0 and < 10 => “Cold, wear a warm coat.”, // 逻辑模式 + 关系模式
>= 10 and < 20 => “Mild, a light jacket is enough.”,
>= 20 and < 30 => “Warm, short sleeves should be fine.”,
>= 30 => “Hot, stay hydrated and seek shade.” // 关系模式
};
Console.WriteLine($”Temperature: {temperature}°C, Suggestion: {weatherSuggestion}”);
// 示例 5: 逻辑模式 (not null, and, or) (C# 9.0+)
string inputString = “Hello”; // 或者 null,或者 “”
string stringCheck = inputString switch
{
not null and { Length: > 0 } => $”Non-empty string of length {inputString.Length}”, // 非 null 且长度大于 0
“” or null => “Empty or null string”, // 空字符串或 null
_ => “Unexpected value” // 理论上前面已经覆盖了所有 string/null 的情况,但 _ 提供了安全网
};
Console.WriteLine($”String check: ‘{inputString}’ -> {stringCheck}”);
“`
3.5 模式匹配中的顺序问题
在 switch 语句和 switch 表达式中使用模式匹配时,分支的顺序至关重要。编译器会从上到下依次检查每个模式。一旦找到第一个匹配成功的模式,就会执行相应的代码块(或评估表达式)并退出 switch。
这意味着,更具体的模式必须放在更通用的模式之前,否则更具体的模式将永远无法被匹配到(因为通用模式会先匹配成功)。
错误顺序示例 (更通用模式在前):
“`csharp
object item = “a string”;
// 错误示例:类型模式 object 放在 string 前面
string itemType = item switch
{
object o => “Is an object”, // 这个模式会匹配所有非 null 的 item,包括 string
string s => “Is a string”, // 这个模式将永远不会被达到,除非 item 是 null
_ => “Is null” // 丢弃模式处理 null
};
Console.WriteLine(itemType); // Output: Is an object (Incorrect)
“`
正确顺序示例 (更具体模式在前):
“`csharp
object item = “a string”;
// 正确示例:类型模式 string 放在 object 前面
string itemType = item switch
{
string s => “Is a string”, // 先检查是否是 string
object o => “Is an object”, // 如果不是 string,再检查是否是 object (非 null)
_ => “Is null” // 最后处理 null
};
Console.WriteLine(itemType); // Output: Is a string (Correct)
“`
对于常量模式和类型模式,编译器通常可以检测到不可达的 case 并给出警告。但对于复杂的模式组合(如涉及 when 子句、属性模式、位置模式),开发者需要自己仔细考虑模式的覆盖范围和顺序,以避免逻辑错误。
第四章:switch 的应用场景
switch 语句和表达式,尤其是结合模式匹配后,在 C# 编程中有很多实用的应用场景:
-
根据枚举值执行不同操作或返回不同结果: 这是传统
switch最常见的用途。
“`csharp
// 传统 switch 语句
enum Command { Start, Stop, Restart }
Command cmd = Command.Start;
switch (cmd) { / … / }// switch 表达式
string action = command switch { Command.Start => “Starting…”, / … / };
“` -
处理多种可能的输入类型 (多态性处理): 使用类型模式可以优雅地处理不同类型的对象。
“`csharp
// 处理不同形状的例子
public abstract class Shape {}
public class Circle : Shape { public double Radius { get; set; } }
public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } }double CalculateArea(Shape shape) => shape switch
{
Circle c => Math.PI * c.Radius * c.Radius,
Rectangle r => r.Width * r.Height,
null => throw new ArgumentNullException(nameof(shape)),
_ => throw new ArgumentException(“Unknown shape type”)
};
“` -
根据对象的属性值或状态进行分支: 使用属性模式可以基于对象的内部状态进行判断。
csharp
string GetOrderStatusDescription(Order order) => order switch
{
{ Status: OrderStatus.Processing, TotalAmount: > 1000 } => "Large order being processed.",
{ Status: OrderStatus.Processing } => "Processing order.",
{ Status: OrderStatus.Shipped, ShippingInfo: { Carrier: "FedEx" } } => "Shipped via FedEx.",
{ Status: OrderStatus.Shipped } => "Order has shipped.",
_ => "Other order status."
}; -
解析结构化数据 (如元组、记录): 使用位置模式可以方便地解构和匹配元组或记录的值。
csharp
// 处理 (x, y) 坐标元组
string GetPointDescription((int x, int y) point) => point switch
{
(0, 0) => "Origin",
( > 0, > 0) => "Quadrant 1",
( < 0, > 0) => "Quadrant 2",
_ => "Other location"
}; -
替代复杂的
if-else if链: 当if-else if链变得很长且基于相似的条件(尤其是涉及相同变量的不同范围、类型或属性)时,考虑使用switch表达式和模式匹配可以提高代码的可读性和简洁性。 -
实现简单的状态机: 虽然更复杂的状态机可能需要专门的库,但对于简单的状态转换,
switch语句可以用来根据当前状态和输入事件决定下一个状态和执行的操作。
第五章:switch 的最佳实践
为了编写清晰、可维护且高效的代码,使用 switch 时应遵循一些最佳实践:
- 选择合适的
switch类型: 如果你的目标是基于输入执行不同的 操作 或修改 状态,使用传统的switch语句。如果你的目标是基于输入计算并 返回 一个值,优先考虑使用switch表达式。 - 保持
case/分支简洁: 避免在case或分支中编写过多复杂的逻辑。如果逻辑复杂,考虑将代码提取到单独的方法中,在case中只调用该方法。 - 善用
default或丢弃模式_: 始终考虑输入值不匹配任何显式case的情况。使用default(语句) 或_(表达式) 来处理这些情况,可以作为错误捕获、日志记录或提供默认行为的安全网。对于switch表达式,确保穷尽性通常是必需的。 - 注意模式匹配的顺序: 当使用模式匹配时,确保更具体的模式出现在更通用的模式之前,以避免意外行为或不可达代码。
- 使用
when子句处理复杂条件: 当简单的模式不足以表达分支条件时,使用when子句添加额外的布尔判断。 - 保持一致性: 在同一个项目中,尽量保持使用
switch的风格一致,例如default或_的位置。 - 文档化: 对于复杂的
switch逻辑,特别是涉及到模式匹配和when子句时,添加注释说明每个分支匹配的条件和目的,可以大大提高代码的可读性。 - 避免过度使用
goto case:goto case虽然提供了显式穿透的能力,但它会破坏代码的顺序执行流程,使代码难以理解和调试。除非有非常明确和充分的理由(并且代码量非常小),否则尽量避免使用goto case。多case标签共享代码的场景,通过将多个case标签连写的方式来实现更清晰。
第六章:switch 与 if-else if 的比较
switch 语句/表达式和 if-else if 链都可以用于实现条件分支。选择哪一个取决于具体的场景:
-
可读性:
- 当基于单个表达式的 精确值 进行分支时,
switch语句通常比等效的if-else if链更清晰、更易读,尤其是有多个值需要检查时。 - 当需要基于 范围、多种变量 的组合、或复杂的布尔表达式进行分支时,
if-else if链通常更灵活、更直观。 - 结合模式匹配的
switch(尤其是表达式)在处理基于 类型、属性 或 解构值 的分支时,可以比等效的if-else if结合is检查、属性访问等方式更加简洁和富有表现力。
- 当基于单个表达式的 精确值 进行分支时,
-
性能:
- 对于传统的
switch语句,编译器有时可以对其进行优化,例如使用跳转表,这在匹配大量连续的整数值时可能比if-else if链稍快。 - 对于字符串
switch,C# 编译器会使用哈希表或其他高效机制进行查找。 - 对于涉及模式匹配的现代
switch,编译器也会进行优化。 - 在大多数实际应用中,
switch和if-else if之间的性能差异通常可以忽略不计,选择更具可读性的结构往往更重要。
- 对于传统的
-
灵活性:
if-else if链更灵活,它可以基于任何布尔表达式进行分支,条件之间可以完全不相关。- 传统的
switch只能基于一个表达式的精确值进行分支。 - 现代
switch结合模式匹配大大提高了灵活性,使其能够处理更复杂的条件,但仍然围绕着匹配一个输入表达式的“形状”或“特征”展开。
总结比较:
- 使用
switch语句: 当你有多个离散值需要与一个表达式进行精确相等比较,并执行不同的操作时。 - 使用
switch表达式: 当你需要根据一个表达式的值或模式来计算并返回一个值时,特别是结合模式匹配时,它可以替代冗长的if-else if赋值链。 - 使用
if-else if链: 当你的条件是基于范围、多个变量的组合、或者复杂的、不相关的布尔表达式时。
结论
C# 的 switch 语句经历了显著的演变。从最初的简单值匹配工具,它已经发展成为一个强大且富有表现力的控制流和模式匹配机制。
传统的 switch 语句依然是处理基于离散值进行分支的有效方法,其强制 break 的设计提高了代码的安全性。
随着 C# 8.0 引入的 switch 表达式和 C# 7.0 起逐步增强的模式匹配能力,switch 在处理更复杂的条件、基于类型和结构进行分支、以及实现“值到值”映射方面变得异常强大和简洁。属性模式、位置模式、关系模式和逻辑模式,配合 when 子句,使得 switch 能够优雅地处理许多原本需要复杂 if-else if 结构才能实现的逻辑。
理解并熟练掌握不同版本的 switch 的语法、功能和最佳实践,可以帮助开发者编写出更清晰、更简洁、更高效的 C# 代码。在实际开发中,根据具体的场景和需求,灵活选择传统的 switch 语句、现代的 switch 表达式,或是 if-else if 链,是优秀程序员必备的技能。