Toán tử điều kiện, thường gọi là toán tử ba ngôi (ternary operator), chọn giữa hai giá trị trong một biểu thức duy nhất: condition ? valueIfTrue : valueIfFalse.
Đầu ra:
0 items, shipping 4.99
1 item, shipping 4.99
5 items, shipping 0
Điều kiện phải là bool. Sau khi điều kiện được tính, chỉ một trong hai nhánh chạy; nhánh còn lại không bao giờ được tính. Nhờ vậy toán tử này an toàn cho việc truy cập có kiểm tra như list.Count > 0 ? list[0] : "empty", trong khi tính list[0] trên một list rỗng sẽ ném ngoại lệ.
Toán tử ba ngôi so với if/else
Toán tử ba ngôi là một biểu thức: nó tạo ra một giá trị để bạn gán, truyền vào hàm hoặc trả về. Câu lệnh if thì chạy các câu lệnh. Hai phương thức sau làm cùng một việc:
// with if/else
static string Status(int stock)
{
if (stock > 0)
return "in stock";
else
return "sold out";
}
// with the ternary
static string Status(int stock) => stock > 0 ? "in stock" : "sold out";
Dùng toán tử ba ngôi khi cả hai nhánh là những giá trị ngắn cùng loại. Dùng if khi một nhánh chạy nhiều câu lệnh, có tác dụng phụ, hoặc khi điều kiện dài đến mức cả dòng không còn vừa vặn dễ đọc.
Toán tử ba ngôi không thể thay thế một câu lệnh if chỉ để thực hiện hành động. Đoạn này không biên dịch được:
stock > 0 ? Ship(order) : Refund(order);
// error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
Cũng không có toán tử ba ngôi thiếu else: ? và : luôn bắt buộc, vì biểu thức phải có giá trị trong cả hai trường hợp.
Hai nhánh cần một kiểu chung
Trình biên dịch suy ra kiểu của cả biểu thức từ hai nhánh. Một nhánh phải chuyển đổi ngầm được sang kiểu của nhánh kia. Khi không nhánh nào làm được, bạn gặp lỗi CS0173:
var discount = isMember ? 10 : null;
// error CS0173: Type of conditional expression cannot be determined because
// there is no implicit conversion between 'int' and '<null>'
var label = inStock ? 1 : "none";
// error CS0173: ... between 'int' and 'string'
Cách sửa là ép một nhánh sang kiểu bạn muốn, để nhánh kia chuyển đổi theo:
Đầu ra:
no discount
7
3.5
points / 2.0 : 0 không cần ép kiểu: 0 là int, chuyển đổi ngầm được sang double, nên biểu thức có kiểu double.
C# 9 bổ sung target typing: khi biểu thức được gán cho một kiểu đã khai báo, trình biên dịch dùng kiểu đó. int? discount = isMember ? 10 : null; biên dịch được từ C# 9 trở đi. Với var thì không có kiểu đích, nên var discount = isMember ? 10 : null; vẫn lỗi ở mọi phiên bản.
Độ ưu tiên và dấu ngoặc
Toán tử điều kiện có độ ưu tiên rất thấp, thấp hơn +, == và &&. Điều đó tiện lợi trong a > b ? a : b, nhưng gây bất ngờ khi nối chuỗi:
Console.WriteLine("Status: " + isActive ? "on" : "off");
// error CS0029: Cannot implicitly convert type 'string' to 'bool'
Trình biên dịch đọc điều kiện là "Status: " + isActive, một chuỗi. Đặt dấu ngoặc quanh toán tử ba ngôi là sửa được: "Status: " + (isActive ? "on" : "off").
Nội suy chuỗi có cái bẫy riêng. Bên trong { }, dấu hai chấm mở đầu phần định dạng ({price:F2}), nên một toán tử ba ngôi không có ngoặc sẽ làm trình phân tích bối rối. Hãy bọc nó trong dấu ngoặc tròn:
Đầu ra:
2 users online
You have 1 unread message
Status: online
Toán tử ba ngôi lồng nhau
Toán tử này kết hợp từ phải sang, nên a ? x : b ? y : z được nhóm thành a ? x : (b ? y : z). Một chuỗi ngắn, mỗi điều kiện một dòng, vẫn dễ đọc:
Đầu ra:
512 B
20 KB
5 MB
Khi vượt quá hai hoặc ba mức, hoặc khi các điều kiện kiểm tra những thứ khác nhau, hãy chuyển sang if/else if hoặc switch expression. Lồng bên trong nhánh đúng (a ? (b ? x : y) : z) là kiểu khó theo dõi nhất.
?? và ?. thay cho những toán tử ba ngôi quen thuộc
Nhiều toán tử ba ngôi chỉ tồn tại để xử lý null. C# có những toán tử ngắn hơn cho việc này:
| Toán tử ba ngôi | Dạng ngắn hơn | Ý nghĩa |
|---|---|---|
name != null ? name : "guest" | name ?? "guest" | giá trị dự phòng khi null |
customer != null ? customer.Email : null | customer?.Email | truy cập thành viên chấp nhận null |
c != null && c.Address != null ? c.Address.City : "unknown" | c?.Address?.City ?? "unknown" | kết hợp cả hai |
value != null ? value : throw new ArgumentNullException(...) | value ?? throw new ArgumentNullException(...) | báo lỗi sớm |
Đầu ra:
Lena from Berlin
guest from unknown
guest from unknown
Biểu thức throw cũng được phép ở bất kỳ nhánh nào của toán tử ba ngôi: int age = input >= 0 ? input : throw new ArgumentOutOfRangeException(nameof(input));.
Toán tử điều kiện ref
Từ C# 7.2, toán tử ba ngôi có thể chọn giữa hai biến thay vì hai giá trị, và bạn có thể gán thông qua kết quả:
// C# 7.2 and later
int wins = 0, losses = 0;
bool won = true;
ref int counter = ref (won ? ref wins : ref losses);
counter++; // increments wins
Cả hai nhánh phải là ref tới các biến cùng kiểu. Cách này hiếm gặp trong code ứng dụng; nó chủ yếu dành cho code cần hiệu năng cao, làm việc với struct hoặc mảng lớn mà không sao chép chúng.
Câu hỏi thường gặp
Toán tử ba ngôi trong C# là gì?
Đó là toán tử điều kiện condition ? valueIfTrue : valueIfFalse, toán tử duy nhất của C# có ba toán hạng. Nó tính điều kiện, rồi tính và trả về đúng một trong hai giá trị: string label = age >= 18 ? "adult" : "minor";.
Có thể dùng toán tử ba ngôi mà không có else trong C# không?
Không. Cả phần ? lẫn phần : đều bắt buộc, vì biểu thức luôn phải cho ra một giá trị. Nếu bạn chỉ muốn làm gì đó khi điều kiện đúng, hãy dùng câu lệnh if. Toán tử ba ngôi cũng không thể đứng riêng như một câu lệnh: x > 0 ? A() : B(); gây lỗi CS0201.
Vì sao tôi gặp lỗi "Type of conditional expression cannot be determined"?
Hai nhánh không có kiểu chung, ví dụ var x = found ? 42 : null; (int và null) hoặc found ? 1 : "none". Hãy ép kiểu một nhánh để có một kiểu chung: found ? (int?)42 : null. Từ C# 9, một kiểu đích được khai báo cũng giải quyết được: int? x = found ? 42 : null; biên dịch được, nhưng var vẫn lỗi.
Làm sao dùng toán tử ba ngôi bên trong nội suy chuỗi?
Bọc nó trong dấu ngoặc tròn: $"{(count == 1 ? "item" : "items")}". Không có ngoặc, dấu : bị hiểu là phần mở đầu của định dạng (format specifier) và code không biên dịch được.
Có thể lồng toán tử ba ngôi trong C# không?
Có. Toán tử này kết hợp từ phải sang, nên a ? x : b ? y : z nghĩa là a ? x : (b ? y : z). Một mức lồng được xuống dòng rõ ràng vẫn dễ đọc; chuỗi sâu hơn sẽ rõ hơn khi viết bằng if/else if hoặc switch expression.