Wyrażenia języka C#

Wskazówka

Ten artykuł jest częścią sekcji Podstawy dla deweloperów, którzy już znają co najmniej jeden język programowania i uczą się języka C#. Jeśli dopiero zaczynasz programować, najpierw zacznij od samouczków Wprowadzenie .

Pochodzi z innego języka? Wyrażenia w języku C# działają podobnie jak w Java, C++i JavaScript. Warto zwrócić uwagę na jedną różnicę: złożone operatory przypisania, takie jak +=, oraz operator inkrementacji ++ są w języku C# wyrażeniami, więc mogą występować w bardziej złożonych wyrażeniach.

Wyrażenie jest fragmentem kodu, który kompilator oblicza, aby wygenerować wartość — liczbę, ciąg, odwołanie lub bool. Na przykład 3 + 4 * 2 jest wyrażeniem, które przyjmuje wartość liczby całkowitej 11, a total > 10 jest wyrażeniem, które przyjmuje wartość true lub false.

Najprostsze wyrażenia to literały (na przykład 42 lub "hello") i nazwy zmiennych (na przykład total). Kompilujesz bardziej złożone wyrażenia, łącząc prostsze z operatorami.

Wyrażenia i instrukcje

Wyrażenie generuje wartość. Polecenie to kompletna instrukcja, którą wykonuje program. Wiele instrukcji zawiera wyrażenia. Na przykład int total = 3 + 4 * 2; jest to instrukcja deklaracji zmiennej. Kompilator oblicza wyrażenie 3 + 4 * 2inicjatora , które generuje 11, i przypisuje wartość do nowej zmiennej total.

Wyrażenie takie jak fraza i instrukcja języka C# można traktować jak pełne zdanie. Te definicje robocze pomagają zrozumieć formalną terminologię i wyjaśnienia, które napotkasz, gdy dowiesz się więcej o języku C#, choć zwykłe kodowanie rzadko wymaga zapamiętania rozróżnienia świadomie.

Łączenie wyrażeń

Wyrażenia można łączyć. Rozważmy3 + 4 * 2. To pojedyncze wyrażenie zawiera dwa mniejsze wyrażenia: wyrażenie 4 * 2mnożenia i wyrażenie3 + <result> dodawania. Gdy wyrażenia są łączone, język C# wymaga reguły, aby zdecydować, która z nich ma być oceniana jako pierwsza. Ta reguła jest pierwszeństwem operatora.

Nie musisz zapamiętywać pełnej hierarchii pierwszeństwa. W następnej sekcji podsumowano codzienne grupy, a nawiasy zawsze pozwalają na jawne określanie kolejności, gdy nie masz pewności.

Priorytet operatorów

Priorytet operatorów określa, w jaki sposób wyrażenie złożone jest grupowane na podwyrażenia. Ta koncepcja jest podobna do reguł kolejności operacji z klasy matematycznej. Operator o wyższym priorytecie wiąże się ściślej z operandami. Wyrażenie ma taką strukturę, jak gdyby te operandy były razem ujęte w nawiasy. W 3 + 4 * 2, * ma wyższy priorytet niż +, więc wyrażenie jest grupowane jako 3 + (4 * 2), a nie (3 + 4) * 2.

Język C# ma więcej grup priorytetów niż cztery podsumowane tutaj. Te dodatkowe grupy wymuszają znane reguły — na przykład mnożenie przed dodawaniem — i obejmują pełny zestaw operatorów. Następujące grupy obejmują najczęściej spotykane elementy w codziennym kodzie:

  1. Podstawowe, jednoargumentowe i zakres — są to trzy odrębne grupy pierwszeństwa, z których wszystkie wiążą się ściślej niż arytmetyczne. To podsumowanie łączy je w jeden krok, ponieważ ze względów praktycznych wszystkie obowiązują przed działaniami arytmetycznymi.
    • Operatory pierwszorzędne — dostęp do składowych (x.y), wywołania metod (f()), indeksowanie (a[i]) i dostęp warunkowy względem wartości null (?., ?[]) — mają najwyższy priorytet wiązania.
    • Operatory unarne działają na pojedynczym operandzie: negacja (-x), logiczne zaprzeczenie (!flag), inkrementacja prefiksowa (++i) i inkrementacja postfiksowa (i++).
    • Operator zakresu (..) służy do tworzenia zakresów indeksów dla wyrażeń wycinków, takich jak array[1..4].
  2. Arytmetyka*, /, % wiążą silniej niż + i -. Mnożenie i dzielenie następuje przed dodatkami i odejmowaniem.
  3. Porównanie<, <=, >, >=, ==, != wiążą się słabiej niż operatory arytmetyczne, więc działania arytmetyczne są wykonywane przed porównaniem.
  4. Logiczne&& i || wiążą najsłabiej spośród typowych operatorów, więc porównania są wykonywane przed połączeniem logicznym.

To pierwszeństwo oznacza, że wyrażenie score + bonus > threshold && attempts < maxAttempts jest obliczane dokładnie tak, jak byś je odczytał: dodaj score i bonus, porównaj sumę z threshold, porównaj maxAttempts z attempts, a następnie połącz oba wyniki bool za pomocą &&. Dodanie nawiasów w celu jawnego pokazania każdej grupy pokazuje tę samą strukturę: ((score + bonus) > threshold) && (attempts < maxAttempts).

Aby uzyskać pełną hierarchię pierwszeństwa obejmującą każdy operator, zobacz Operatory i wyrażenia języka C#.

Użyj nawiasów, aby wyjaśnić intencję

Nawiasy zmieniają kolejność wykonywania działań i jednocześnie pokazują twój zamiar. Jeśli kolejność nie jest oczywista z powyższych grup, dodaj nawiasy:

int score = 80;
int bonus = 15;
int threshold = 90;
bool eligible = true;

// Without parentheses: || binds less tightly than &&, so this reads as:
//   eligible && (score + bonus > threshold) || bonus > 20
bool result1 = eligible && score + bonus > threshold || bonus > 20;

// With parentheses: forces the || to combine two complete conditions
bool result2 = (eligible && score + bonus > threshold) || bonus > 20;

Console.WriteLine(result1); // => True
Console.WriteLine(result2); // => True

// Parentheses can also change the result:
bool isAdmin = false;
bool isOwner = true;

// Without: && binds tighter, so: (isAdmin && isOwner) || true
bool access1 = isAdmin && isOwner || true;
// With: forces the || to run first
bool access2 = isAdmin && (isOwner || true);

Console.WriteLine(access1); // => True   (false && true is false; false || true is true)
Console.WriteLine(access2); // => False  (false && anything is false)

Ostatnie dwa wiersze pokazują, że nawiasy mogą zmienić wynik, a nie tylko styl. Gdy && i || pojawi się razem, dodaj nawiasy, aby ustalić, który warunek łączy się najpierw. Czytelnik, który widzi (isAdmin && isOwner) || isSuperUser , zna intencję natychmiast.

Jak są oceniane wyrażenia

Zrozumienie, jak język C# ocenia wyrażenia połączone, jest łatwiejszy w analogii. Pomyśl o pracy z złożonym problemem matematycznym z ołówkiem i papierem: identyfikujesz najbardziej wewnętrzne lub najwyższe pierwszeństwo podwyrażenia, obliczasz jego wartość tymczasową, zapisujesz wynik, a następnie powtarzaj przy użyciu następnego wyrażenia podrzędnego — kontynuując, aż osiągniesz ostateczną odpowiedź.

Język C# jest zgodny z tym samym procesem, kierowanym przez dwie reguły:

Reguła 1: Operandy są obliczane od lewej do prawej. W przypadku dowolnego wyrażenia binarnego oba operandy muszą być w pełni oceniane przed zastosowaniem operatora. C# najpierw oblicza lewy operand, następnie prawy, a potem wykonuje operację.

Reguła 2: Niektóre operatory stosują skrócone obliczanie. Niektóre operatory przestają oceniać, gdy tylko zostanie określony wynik, pomijając pozostałe operandy:

  • && (warunkowy operator AND): zwraca false, gdy lewa strona ma wartość false. Prawa strona nigdy nie jest oceniana.
  • || (alternatywa warunkowa): zwraca wartość true, gdy lewa strona ma wartość true. Prawa strona nigdy nie jest oceniana.
  • ?: (warunkowe/ternarne): oblicza tylko gałąź zgodną z warunkiem — druga gałąź nigdy nie jest oceniana.
  • ?. (dostęp warunkowy o wartości null) i ?[] (dostęp do elementu warunkowego o wartości null): zatrzymaj i wróć null natychmiast, gdy lewa strona to null, pomijając dostęp do składowych lub indeks.
  • ??= (przypisanie z operatorem łączenia z wartością null): przypisuje prawą stronę tylko wtedy, gdy lewa strona ma wartość null.

Ocena papieru i ołówka

Rozważ wyrażenie 3 + 6 / 2. Mimo że dodanie pojawia się najpierw w kolejności odczytu, / ma wyższy priorytet niż +, więc 6 / 2 jest wyrażenie podrzędne, które najpierw oblicza. Praca z nim krok po kroku — dokładnie tak jak na papierze:

3 + 6 / 2
      ↓   (evaluate 6 / 2 → 3)
3 +   3
  ↓   (evaluate 3 + 3 → 6)
  6
// 3 + 6 / 2
// Step 1: 6 / 2 has higher precedence → interim value 3
// Step 2: 3 + 3 → final result 6
int result = 3 + 6 / 2;
Console.WriteLine(result); // => 6

Tymczasowa wartość 3 wygenerowana przez element 6 / 2 staje się prawym operandem elementu +, a wynikiem końcowym jest 6. Każde wyrażenie podrzędne generuje wartość tymczasową; te wartości tymczasowe są źródłem następnego wyrażenia podrzędnego, dopóki tylko jedna wartość nie pozostanie.

Łączność to powiązane pojęcie: gdy dwa operatory mają ten sam priorytet, łączność decyduje, który z nich jest wykonywany jako pierwszy. Większość operatorów języka C# jest lewostronnie łączna, co oznacza, że są grupowane od lewej do prawej. Tak więc a - b - c jest to samo, co (a - b) - c, a nie a - (b - c).

Ocena zwarciowa w praktyce

Ocena zwarciowa jest szczególnie przydatna w przypadku sprawdzania wartości null:

string? text = null;

// Safe: second condition runs only when text is not null
bool hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => False  (short-circuits after null check; no NullReferenceException)

text = "hello";
hasContent = text != null && text.Length > 0;
Console.WriteLine(hasContent); // => True

// || short-circuits on true: right side is never evaluated when left side is true
string word = "hello";
bool anyMatch = word.StartsWith("h") || word.StartsWith("x");
Console.WriteLine(anyMatch); // => True  (right side never evaluated)

// ?. short-circuits on null: returns null without accessing .Length
string? maybeNull = null;
int? length = maybeNull?.Length;   // length is null; no NullReferenceException
Console.WriteLine(length.HasValue); // => False

text != null && text.Length > 0 jest bezpieczny, ponieważ drugi warunek jest wykonywany tylko wtedy, gdy text nie jest null. Podobnie ?. zatrzymuje obliczanie po napotkaniu odwołania null, co pozwala uniknąć NullReferenceException bez jawnego sprawdzenia if.

Aby uzyskać szersze informacje na temat operatorów bezpiecznego dostępu dla wartości null, zobacz Operatory null w języku C#.

Zobacz także