Operator przyjmuje jeden, dwa lub trzy operandy i daje wartość: a + b, !done, x ?? fallback. Operatory C# są bliskie tym z C, Javy i JavaScriptu, ale kilka zachowuje się w sposób zaskakujący dla początkujących, przede wszystkim dzielenie całkowite i znak %.
Operatory arytmetyczne
+, -, *, / i % działają na każdym typie liczbowym. Typ wyniku zależy od operandów: dwie wartości int dają int, a int i double dają double.
Wynik:
22
12
85
3
2
3.4
3.4
3
3.4
Dzielenie całkowite obcina w stronę zera. 17 / 5 daje 3, a -17 / 5 daje -3. To źródło bardzo częstego błędu: int percent = done / total * 100; daje 0 za każdym razem, gdy done < total, bo done / total już jest 0. Najpierw pomnóż (done * 100 / total) albo dziel jako double.
Dzielenie liczby całkowitej lub decimal przez zero rzuca DivideByZeroException. Dzielenie double przez zero nie rzuca wyjątku; daje nieskończoność albo NaN dla 0.0 / 0.0:
Wynik:
True
True
False
DivideByZeroException
Operator modulo i liczby ujemne
% zwraca resztę z dzielenia, a jego wynik ma znak lewego operandu. To różni go od Pythona i od matematycznego "mod", które przy dodatnim dzielniku są zawsze nieujemne:
Wynik:
1
-1
1
2
even
62 min 5 s
Thu
1.5
Używaj funkcji pomocniczej Mod zawsze, gdy lewa strona może być ujemna, a wynik jest indeksem: days[(today + offset) % 7] rzuciłoby IndexOutOfRangeException dla ujemnej sumy. Sprawdzanie liczb nieparzystych ma tę samą pułapkę: n % 2 == 1 daje false dla ujemnych liczb nieparzystych; sprawdzaj n % 2 != 0.
Przypisanie, przypisanie złożone i inkrementacja
= przypisuje. Formy złożone +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= łączą operację z przypisaniem. ++ i -- dodają lub odejmują 1 i występują w formie przedrostkowej i przyrostkowej, które różnią się zwracaną wartością:
Wynik:
14
start, step
a=5 b=7 i=7
4
Przypadek byte pokazuje pewne dziwactwo: level = level + 10; się nie kompiluje, bo byte + int to int, ale level += 10; się kompiluje, bo przypisanie złożone zawiera niejawne rzutowanie z powrotem na typ zmiennej. To rzutowanie zawija wartość przy przepełnieniu (250 + 10 daje 4).
Operatory porównania i logiczne
Operatory porównania (==, !=, <, >, <=, >=) zwracają bool. Dla stringów == porównuje tekst, a nie referencje. Operatory logiczne to ! (nie), && (i), || (lub) i ^ (alternatywa wykluczająca).
Wynik:
True
no active user
with ||:
evaluated A
with |:
evaluated A
evaluated B
True
&& i || skracają obliczenia: prawy operand wykonuje się tylko wtedy, gdy może zmienić wynik. Właśnie dlatego user != null && user.IsActive jest bezpieczne. Pojedyncze & i | na wartościach logicznych zawsze obliczają obie strony, a w warunku rzadko tego chcesz.
Operatory bitowe i przesunięcia
Na typach całkowitych &, |, ^ i ~ działają na pojedynczych bitach, a << i >> je przesuwają. Używa się ich do flag, masek, haszowania i niskopoziomowych protokołów:
Wynik:
3
True
False
101
1024
125
5
-1
Dla nazwanych zestawów flag enum z atrybutem [Flags] jest czytelniejszy niż stałe całkowite, a HasFlag czyta się lepiej niż maskę. C# 11 dodał >>>, przesunięcie w prawo bez znaku, które wypełnia zerami nawet liczby ujemne.
Operatory null
Do pracy z wartościami, które mogą być null, służą trzy operatory:
Wynik:
Guest
0
5
a ?? b zwraca a, chyba że jest null, wtedy b. a?.Member zwraca null zamiast rzucać wyjątek, gdy a jest null. C# 8 dodał a ??= b, które przypisuje b do a tylko wtedy, gdy a jest null. Szczegółowo opisują je typy nullable.
Priorytet operatorów
Gdy wyrażenie łączy operatory, wyższe wiersze wiążą silniej. Operatory z tego samego wiersza grupują się od lewej do prawej, z wyjątkiem przypisania, ?? i ?:, które grupują się od prawej do lewej. (Same operandy są zawsze obliczane od lewej do prawej.)
| Kategoria | Operatory |
|---|---|
| Podstawowe | x.y, x?.y, f(x), a[i], x++, x--, new, typeof, nameof |
| Jednoargumentowe | +x, -x, !x, ~x, ++x, --x, (T)x, await |
| Multiplikatywne | *, /, % |
| Addytywne | +, - |
| Przesunięcia | <<, >>, >>> |
| Relacyjne i sprawdzanie typu | <, >, <=, >=, is, as |
| Równość | ==, != |
| Bitowe AND | & |
| Bitowe XOR | ^ |
| Bitowe OR | | |
| Warunkowe AND | && |
| Warunkowe OR | || |
| Null coalescing | ?? |
| Warunkowy | c ? a : b |
| Przypisanie i lambda | =, +=, -=, ??=, => oraz pozostałe formy złożone |
Dwie konsekwencje warte poznania: a + b * c najpierw mnoży, a x & mask == 0 najpierw porównuje (== wiąże silniej niż &), więc oznacza x & (mask == 0) i nie kompiluje się dla liczb całkowitych. Pisz (x & mask) == 0. Nawiasy nic nie kosztują, a intencja staje się oczywista.
Przeciążanie operatorów
Twoje własne typy mogą definiować znaczenie operatorów. W ten sposób decimal, DateTime i TimeSpan obsługują + i <:
Wynik:
33.49
True
Operatory muszą być public static, a niektóre występują w wymaganych parach: zdefiniowanie < wymaga >, a == wymaga !=. Gdy == jest zdefiniowane, kompilator ostrzega też, dopóki nie nadpiszesz pasujących Equals i GetHashCode. Przeciążaj operatory tylko tam, gdzie ich znaczenie jest oczywiste, jak przy liczbach, wektorach i pieniądzach.
Najczęściej zadawane pytania
Co robi % w C#?
% to operator reszty z dzielenia: 17 % 5 daje 2. Wynik ma znak lewego operandu, więc -7 % 3 daje -1, a nie 2 jak w Pythonie. Jeśli wynik ma być zawsze nieujemny, czego potrzebujesz przy zawijaniu indeksu, napisz ((a % n) + n) % n. Działa też na double i decimal: 7.5 % 2 daje 1.5.
Dlaczego 7 / 2 daje 3 w C#?
Gdy oba operandy są liczbami całkowitymi, / wykonuje dzielenie całkowite i odrzuca część ułamkową, obcinając w stronę zera. Żeby dostać wynik ułamkowy, zrób z jednego operandu wartość zmiennoprzecinkową lub dziesiętną: 7 / 2.0 daje 3.5, (double)a / b konwertuje przed dzieleniem, a 7m / 2 daje 3.5 jako decimal.
Jaka jest różnica między && a & w C#?
&& to warunkowe AND: jeśli lewa strona jest false, prawa nie jest obliczana. & na wartościach logicznych zawsze oblicza obie strony, a na liczbach całkowitych jest bitowym AND. W warunkach używaj && i ||, zwłaszcza w zabezpieczeniach takich jak user != null && user.IsActive, które z & rzuciłyby NullReferenceException.
Jaka jest różnica między i++ a ++i w C#?
Oba dodają 1 do i. Wyrażenie i++ daje starą wartość, a ++i nową. W osobnej linii (i++;) nie ma różnicy. Ma to znaczenie tylko wtedy, gdy wynik jest używany, jak w int a = i++;, i dlatego wiele przewodników stylu każe trzymać inkrementacje w osobnych liniach.
Jaki jest priorytet operatorów w C#?
Od najwyższego do najniższego: podstawowe (x.y, f(x), a[i], x++), jednoargumentowe (!, -, ++x, rzutowania), multiplikatywne (* / %), addytywne (+ -), przesunięcia, relacyjne (< > <= >= is as), równości (== !=), &, ^, |, &&, ||, ??, warunkowy ?: i na końcu przypisanie. W razie wątpliwości dodaj nawiasy.