Câu lệnh switch so sánh một giá trị với danh sách các nhãn case hằng số và chạy phần khớp với giá trị đó. Trong C#, mọi phần đều phải kết thúc bằng break (hoặc một câu lệnh khác giúp thoát khỏi phần đó), nên một case không bao giờ có thể lặng lẽ chạy sang case kế tiếp.
Đầu ra:
200: OK
302: redirect
404: not found
500: unhandled status
Giá trị trong ngoặc được tính một lần rồi so sánh với từng nhãn. default chạy khi không có gì khớp; nó không bắt buộc, và có thể đặt ở bất kỳ đâu trong danh sách, dù thường được đặt cuối cùng. Nếu không có gì khớp và không có default, cả câu lệnh switch không làm gì.
Mỗi phần đều cần break
Trong C, C++, Java và JavaScript, quên break khiến chương trình trượt xuống case tiếp theo. C# biến lỗi đó thành lỗi biên dịch:
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;
}
Quy tắc là điểm cuối danh sách câu lệnh của một phần không được phép chạm tới. break là cách thông dụng để thỏa mãn quy tắc này, nhưng bất cứ thứ gì thoát khỏi phần đó đều được:
returnthoát khỏi toàn bộ phương thức, thường là cách gọn nhất trong một hàm hỗ trợ nhỏ.throwném ngoại lệ cho một giá trị lẽ ra không bao giờ xuất hiện.continuenhảy sang lần lặp tiếp theo khiswitchnằm trong một vòng lặp.goto casevàgoto defaultcố ý nhảy sang một phần khác.
Phần cuối cùng cũng không được miễn. Một default: ở cuối mà không có break sẽ lỗi CS8070, Control cannot fall out of switch from final case label ('default:').
Nhiều giá trị trong một case
Các nhãn không có câu lệnh nào xen giữa sẽ dùng chung phần bên dưới. Đây là kiểu fall through duy nhất mà C# cho phép, vì một nhãn rỗng không thể giấu code nào:
Đầu ra:
'O' is a vowel
'k' is a consonant or symbol
' ' is a space
'7' is a digit
Mỗi phần ở đây kết thúc bằng return, nên không cần break (một break sau return sẽ là code không thể chạm tới). Hai nhãn cùng giá trị là lỗi, CS0152 The switch statement contains multiple cases with the label value '1', và mọi nhãn phải là hằng số lúc biên dịch: đặt một biến trong case sẽ lỗi "a constant value is expected".
Switch trên chuỗi
Sau số nguyên, chuỗi là giá trị được switch nhiều nhất. Phép so sánh là ordinal và phân biệt hoa thường, nên hãy chuẩn hóa đầu vào trước khi switch:
Đầu ra:
Starting the server
Stopping the server
Server is running
Unknown command: restart
Unknown command: (none)
Nếu không có ToLowerInvariant, "STOP" sẽ rơi vào default. Switch trên giá trị null là hợp lệ: nó không khớp hằng số chuỗi nào và rơi vào default, nên bản thân switch không bao giờ ném ngoại lệ.
Switch trên enum
Enum chính là thứ mà switch được tạo ra để xử lý: một tập đóng các giá trị có tên.
Đầu ra:
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
Trình biên dịch không kiểm tra xem câu lệnh switch đã bao phủ mọi thành viên enum hay chưa, và một biến enum có thể chứa bất kỳ số nguyên nào của kiểu nền ((OrderStatus)42 là hợp lệ). Nhánh default ném ngoại lệ biến một case bị bỏ quên thành lỗi xuất hiện ngay và dễ thấy, thay vì lặng lẽ không làm gì.
goto case và goto default
Khi một case thật sự nên chạy tiếp sang case khác, C# buộc bạn viết rõ lệnh nhảy:
Đầu ra:
admin:
manage users
edit posts
read posts
editor:
edit posts
read posts
guest:
read posts
goto case nhận một hằng số phải khớp với một trong các nhãn của cùng câu lệnh switch. Cách này hiếm gặp trong code thực tế: hành vi dùng chung thường rõ ràng hơn khi đặt trong một phương thức mà nhiều case cùng gọi. Dù vậy, đây vẫn là câu trả lời cho câu hỏi "làm sao fall through trong C#".
Biến bên trong case
Toàn bộ khối switch là một phạm vi, nên hai case không thể khai báo biến cùng tên:
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;
}
Bọc phần thân của mỗi case trong dấu ngoặc nhọn để nó có phạm vi riêng: case "square": { int area = side * side; ... break; }.
break bên trong vòng lặp
break trong một case thoát khỏi switch, không phải vòng lặp bao quanh. continue thì ngược lại: switch không phải vòng lặp, nên continue đi tới lần lặp tiếp theo của vòng lặp.
Đầu ra:
end marker found
Total: 134
Vòng lặp vẫn tiếp tục sau "end" và cộng thêm 99. Để dừng vòng lặp từ bên trong switch, hãy đặt một cờ rồi kiểm tra nó sau switch, hoặc chuyển vòng lặp vào một phương thức và dùng return. Trang về break và continue so sánh các lựa chọn này.
Nhãn case dạng pattern và when
Từ C# 7.0, một nhãn case có thể là một pattern thay vì hằng số: nó có thể kiểm tra kiểu của giá trị và thêm điều kiện when. Dạng này phổ biến trong code C# hiện đại:
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}";
}
}
Với pattern, thứ tự rất quan trọng: case khớp đầu tiên sẽ thắng, và trình biên dịch báo lỗi khi một case không bao giờ được tới vì case trước đã bao phủ nó (đặt case int n: trên case int n when n < 0: sẽ lỗi). Mệnh đề when cũng dùng được với case hằng số: case 0 when isAdmin:. C# 9 bổ sung pattern so sánh và pattern logic, nên một case có thể viết case >= 90: hoặc case 'a' or 'e' or 'i':.
Trên C# 7 không dùng pattern, cùng phép phân loại theo kiểu đó là một chuỗi if với type pattern is:
Đầu ra:
negative int -3
int 12
empty string
string "hi"
other: Decimal
nothing
Khi mọi nhánh của switch chỉ tính ra một giá trị, switch expression còn ngắn hơn nữa: var grade = score switch { >= 90 => "A", >= 80 => "B", _ => "C" };. Trang về switch expression trình bày cú pháp, pattern và quy tắc về tính đầy đủ của nó.
Có thể switch trên những gì
| Kiểu giá trị | Case hằng số | Ghi chú |
|---|---|---|
int, long, byte và các kiểu số nguyên khác | có | trường hợp kinh điển |
char | có | case 'a': |
string | có | ordinal, phân biệt hoa thường, null đi vào default |
bool | có | hợp lệ, nhưng if rõ ràng hơn |
mọi enum | có | không kiểm tra tính đầy đủ |
double, decimal | từ C# 7 | chỉ hằng số; khoảng giá trị cần pattern so sánh của C# 9 |
| mọi kiểu khác | từ C# 7 | thông qua type pattern, case Circle c: |
Câu hỏi thường gặp
Có bắt buộc dùng break trong switch của C# không?
Mỗi phần case phải kết thúc bằng một câu lệnh thoát khỏi nó: thường là break, nhưng return, throw, continue (bên trong vòng lặp) hoặc goto case cũng được tính. Chạy hết một phần rồi rơi xuống phần kế tiếp là lỗi biên dịch, CS0163 Control cannot fall through from one case label ('case 1:') to another, và quy tắc này áp dụng cả với phần cuối cùng.
Làm sao xử lý nhiều giá trị trong một case của switch C#?
Xếp chồng các nhãn và không đặt gì giữa chúng: case "sat": case "sun": Console.WriteLine("weekend"); break;. Nhãn case rỗng được phép rơi xuống nhãn kế tiếp; chỉ phần nào có câu lệnh mới phải kết thúc bằng break. Từ C# 9 trở đi, một case dạng pattern còn có thể viết case 1 or 2 or 3:.
Switch trên chuỗi trong C# có phân biệt hoa thường không?
Có. Các case chuỗi được so sánh bằng phép so sánh ordinal có phân biệt hoa thường, nên "Yes" không khớp với case "yes":. Hãy chuẩn hóa giá trị trước bằng switch (input.Trim().ToLowerInvariant()). Một chuỗi null không khớp case hằng số nào và đi vào default.
Switch trong C# có fall through như trong C hay Java không?
Không ngầm định. Khi bạn muốn một case chạy tiếp sang case khác, hãy viết rõ bằng goto case <value>; hoặc goto default;. Cách này khiến mọi lần fall through đều hiện rõ trong code, đó là lý do C# cấm kiểu fall through lặng lẽ.
Câu lệnh switch và switch expression trong C# khác nhau thế nào?
Câu lệnh switch chạy các câu lệnh và cần case, break và default. Switch expression (từ C# 8 trở đi) tạo ra một giá trị: var label = n switch { 0 => "zero", > 0 => "positive", _ => "negative" };. Dùng biểu thức khi mọi nhánh chỉ tính ra một kết quả.