Toán tử (operator) nhận một, hai hoặc ba toán hạng và tạo ra một giá trị: a + b, !done, x ?? fallback. Các toán tử của C# gần giống C, Java và JavaScript, nhưng có vài toán tử hoạt động theo cách khiến người mới bất ngờ, trên hết là phép chia số nguyên và dấu của %.
Toán tử số học
+, -, *, / và % dùng được với mọi kiểu số. Kiểu của kết quả đi theo các toán hạng: hai giá trị int cho ra int, một int và một double cho ra double.
Đầu ra:
22
12
85
3
2
3.4
3.4
3
3.4
Phép chia số nguyên làm tròn về phía 0. 17 / 5 bằng 3 và -17 / 5 bằng -3. Điều này gây ra một bug rất phổ biến: int percent = done / total * 100; luôn bằng 0 khi done < total, vì done / total đã là 0. Hãy nhân trước (done * 100 / total) hoặc chia theo kiểu double.
Chia một số nguyên hoặc decimal cho 0 sẽ ném DivideByZeroException. Chia một double cho 0 thì không ném ngoại lệ; kết quả là vô cực, hoặc NaN với 0.0 / 0.0:
Đầu ra:
True
True
False
DivideByZeroException
Toán tử chia lấy dư và số âm
% trả về phần dư của phép chia, và kết quả mang dấu của toán hạng bên trái. Điều này khác với Python và với phép "mod" trong toán học, vốn luôn không âm khi số chia dương:
Đầu ra:
1
-1
1
2
even
62 min 5 s
Thu
1.5
Hãy dùng hàm hỗ trợ Mod mỗi khi vế trái có thể âm và kết quả là một chỉ số: days[(today + offset) % 7] sẽ ném IndexOutOfRangeException khi tổng là số âm. Kiểm tra số lẻ cũng có cái bẫy tương tự: n % 2 == 1 là false với số lẻ âm; hãy kiểm tra n % 2 != 0.
Phép gán, gán kết hợp và tăng giảm
= dùng để gán. Các dạng kết hợp +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= gộp một phép toán với phép gán. ++ và -- cộng hoặc trừ 1, và có dạng tiền tố lẫn hậu tố, khác nhau ở giá trị mà chúng trả về:
Đầu ra:
14
start, step
a=5 b=7 i=7
4
Trường hợp byte cho thấy một điểm lạ: level = level + 10; không biên dịch được, vì byte + int là một int, nhưng level += 10; thì được, vì phép gán kết hợp có sẵn một phép ép kiểu ngầm về kiểu của biến. Phép ép kiểu đó quay vòng khi tràn số (250 + 10 thành 4).
Toán tử so sánh và logic
Các toán tử so sánh (==, !=, <, >, <=, >=) trả về một bool. Với chuỗi, == so sánh nội dung văn bản, không so sánh tham chiếu. Các toán tử logic là ! (not), && (and), || (or) và ^ (exclusive or, hoặc loại trừ).
Đầu ra:
True
no active user
with ||:
evaluated A
with |:
evaluated A
evaluated B
True
&& và || ngắt mạch (short-circuit): toán hạng bên phải chỉ chạy khi nó có thể thay đổi kết quả. Chính điều đó làm cho user != null && user.IsActive an toàn. Các toán tử đơn & và | trên boolean luôn tính cả hai vế, điều hiếm khi bạn muốn trong một điều kiện.
Toán tử bit và toán tử dịch
Trên các kiểu số nguyên, &, |, ^ và ~ làm việc với từng bit, còn << và >> dịch chuyển các bit. Chúng được dùng cho cờ (flag), mặt nạ bit (mask), hàm băm và các giao thức cấp thấp:
Đầu ra:
3
True
False
101
1024
125
5
-1
Với một tập cờ có tên, enum gắn thuộc tính [Flags] rõ ràng hơn các hằng số nguyên, và HasFlag dễ đọc hơn một mặt nạ bit. C# 11 bổ sung >>>, phép dịch phải không dấu luôn điền số 0 kể cả với số âm.
Các toán tử null
Có ba toán tử dành cho các giá trị có thể là null:
Đầu ra:
Guest
0
5
a ?? b trả về a nếu nó khác null, ngược lại trả về b. a?.Member trả về null thay vì ném ngoại lệ khi a là null. C# 8 bổ sung a ??= b, chỉ gán b cho a khi a là null. Các toán tử này được trình bày chi tiết trong kiểu nullable.
Độ ưu tiên toán tử
Khi một biểu thức trộn nhiều toán tử, các hàng phía trên gắn chặt hơn. Các toán tử cùng một hàng nhóm từ trái sang phải, trừ phép gán, ?? và ?:, vốn nhóm từ phải sang trái. (Bản thân các toán hạng luôn được tính từ trái sang phải.)
| Nhóm | Toán tử |
|---|---|
| Cơ bản | x.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof |
| Một ngôi | +x, -x, !x, ~x, ++x, --x, (T)x, await |
| Nhân | *, /, % |
| Cộng | +, - |
| Dịch bit | <<, >>, >>> |
| Quan hệ và kiểm tra kiểu | <, >, <=, >=, is, as |
| Bằng nhau | ==, != |
| AND theo bit | & |
| XOR theo bit | ^ |
| OR theo bit | | |
| AND có điều kiện | && |
| OR có điều kiện | || |
| Null coalescing | ?? |
| Điều kiện | c ? a : b |
| Gán và lambda | =, +=, -=, ??=, => và các dạng gán kết hợp khác |
Hai hệ quả nên biết: a + b * c nhân trước, còn x & mask == 0 so sánh trước (== gắn chặt hơn &), nên nó có nghĩa là x & (mask == 0) và không biên dịch được với số nguyên. Hãy viết (x & mask) == 0. Dấu ngoặc không tốn gì mà còn làm ý định rõ ràng.
Nạp chồng toán tử
Kiểu do bạn tự định nghĩa có thể quy định ý nghĩa của các toán tử. Đây là cách decimal, DateTime và TimeSpan hỗ trợ + và <:
Đầu ra:
33.49
True
Toán tử phải là public static, và một số toán tử đi theo cặp bắt buộc: định nghĩa < thì phải có >, và == thì phải có !=. Khi đã định nghĩa ==, trình biên dịch còn cảnh báo cho đến khi bạn ghi đè Equals và GetHashCode cho khớp. Chỉ nạp chồng toán tử khi ý nghĩa đã hiển nhiên, như với số, vector và tiền.
Câu hỏi thường gặp
Toán tử % trong C# làm gì?
% là toán tử chia lấy dư: 17 % 5 bằng 2. Kết quả mang dấu của toán hạng bên trái, nên -7 % 3 bằng -1, không phải 2 như trong Python. Để kết quả luôn không âm, như khi cần quay vòng một chỉ số, hãy viết ((a % n) + n) % n. Toán tử này cũng dùng được với double và decimal: 7.5 % 2 bằng 1.5.
Vì sao 7 / 2 bằng 3 trong C#?
Khi cả hai toán hạng đều là số nguyên, / thực hiện phép chia số nguyên và bỏ phần thập phân, làm tròn về phía 0. Hãy biến một toán hạng thành số thực hoặc decimal để có kết quả thập phân: 7 / 2.0 bằng 3.5, (double)a / b chuyển kiểu trước khi chia, còn 7m / 2 bằng 3.5 kiểu decimal.
&& và & trong C# khác nhau thế nào?
&& là AND có điều kiện: nếu vế trái là false thì vế phải không được tính. & trên boolean luôn tính cả hai vế, còn trên số nguyên thì là AND theo bit. Hãy dùng && và || cho điều kiện, nhất là các câu lệnh bảo vệ như user != null && user.IsActive, vốn sẽ ném NullReferenceException nếu dùng &.
i++ và ++i trong C# khác nhau thế nào?
Cả hai đều cộng 1 vào i. Biểu thức i++ cho ra giá trị cũ, còn ++i cho ra giá trị mới. Khi đứng riêng một dòng (i++;) thì không có khác biệt. Điều này chỉ quan trọng khi kết quả được dùng, như trong int a = i++;, vì thế nhiều quy ước code để phép tăng trên một dòng riêng.
Độ ưu tiên toán tử trong C# là gì?
Từ cao xuống thấp: nhóm cơ bản (x.y, f(x), a[i], x++), một ngôi (!, -, ++x, ép kiểu), nhân (* / %), cộng (+ -), dịch bit, quan hệ (< > <= >= is as), bằng nhau (== !=), &, ^, |, &&, ||, ??, toán tử điều kiện ?:, và cuối cùng là phép gán. Khi không chắc, hãy thêm dấu ngoặc.