Operatory w C#

Wprowadzenie do operatorów

Operatory to specjalne symbole lub słowa kluczowe, które wykonują określone operacje na zmiennych, stałych lub wyrażeniach. W C# operatory pozwalają nam:

  • Wykonywać obliczenia matematyczne
  • Porównywać wartości
  • Łączyć warunki logiczne
  • Przypisywać wartości do zmiennych
  • Manipulować bitami i wykonywać inne zaawansowane operacje

Składniki operacji

  • Operand – wartość, na której wykonywana jest operacja
  • Operator – symbol określający rodzaj operacji
  • Wynik – rezultat operacji
int a = 5;     // a i 5 to operandy, = to operator przypisania
int b = 3;     // b i 3 to operandy, = to operator przypisania  
int wynik = a + b;  // a, b to operandy, + to operator dodawania, wynik = 8

Kategorie operatorów

W C# operatory dzielimy na kilka głównych kategorii:

  1. Operatory arytmetyczne – obliczenia matematyczne
  2. Operatory przypisania – przypisywanie wartości
  3. Operatory porównania (relacyjne) – porównywanie wartości
  4. Operatory logiczne – operacje na wartościach logicznych
  5. Operatory bitowe – operacje na bitach
  6. Operatory warunkowe – decyzje i wybory
  7. Operatory typu – sprawdzanie i konwersja typów
  8. Inne operatory – specjalne przypadki

1. Operatory arytmetyczne

Służą do wykonywania podstawowych operacji matematycznych.

Podstawowe operatory arytmetyczne:

OperatorNazwaOpisPrzykład
+DodawanieDodaje dwa operandy5 + 3 = 8
-OdejmowanieOdejmuje drugi operand od pierwszego5 - 3 = 2
*MnożenieMnoży dwa operandy5 * 3 = 15
/DzielenieDzieli pierwszy operand przez drugi6 / 3 = 2
%Modulo (reszta z dzielenia)Zwraca resztę z dzielenia7 % 3 = 1

Przykłady praktyczne:

// Podstawowe operacje
int a = 15;
int b = 4;

int suma = a + b;           // 19
int roznica = a - b;        // 11  
int iloczyn = a * b;        // 60
int iloraz = a / b;         // 3 (dzielenie całkowite!)
int reszta = a % b;         // 3 (15 = 3*4 + 3)

Console.WriteLine($"{a} + {b} = {suma}");
Console.WriteLine($"{a} - {b} = {roznica}");
Console.WriteLine($"{a} * {b} = {iloczyn}");
Console.WriteLine($"{a} / {b} = {iloraz}");
Console.WriteLine($"{a} % {b} = {reszta}");

Dzielenie całkowite vs zmiennoprzecinkowe:

// Dzielenie całkowite
int x = 7;
int y = 2;
int wynikInt = x / y;           // 3 (część dziesiętna odrzucona)
int resztaInt = x % y;          // 1

// Dzielenie zmiennoprzecinkowe  
double wynikDouble = (double)x / y;    // 3.5 (rzutowanie na double)
double wynikDouble2 = 7.0 / 2.0;       // 3.5 (literały double)

Console.WriteLine($"Dzielenie całkowite: {x}/{y} = {wynikInt}, reszta = {resztaInt}");
Console.WriteLine($"Dzielenie rzeczywiste: {wynikDouble}");

Operatory unarne (jeden operand):

int liczba = 5;

int dodatnia = +liczba;     // +5 (operator plus unarny)
int ujemna = -liczba;       // -5 (operator minus unarny)  
int zwiększona = ++liczba;  // 6 (pre-increment)
int zmniejszona = --liczba; // 5 (pre-decrement)

Console.WriteLine($"Oryginalna: {5}");
Console.WriteLine($"Dodatnia: {dodatnia}");
Console.WriteLine($"Ujemna: {ujemna}");

Increment i decrement – różnice:

int a = 5;
int b = 5;

// Pre-increment/decrement - najpierw zmiana, potem użycie
int wynik1 = ++a;  // a = 6, wynik1 = 6
int wynik2 = --b;  // b = 4, wynik2 = 4

Console.WriteLine($"Pre-increment: a={a}, wynik1={wynik1}");
Console.WriteLine($"Pre-decrement: b={b}, wynik2={wynik2}");

// Post-increment/decrement - najpierw użycie, potem zmiana
int c = 5;
int d = 5;

int wynik3 = c++;  // wynik3 = 5, potem c = 6
int wynik4 = d--;  // wynik4 = 5, potem d = 4

Console.WriteLine($"Post-increment: c={c}, wynik3={wynik3}");
Console.WriteLine($"Post-decrement: d={d}, wynik4={wynik4}");

Operatory arytmetyczne z innymi typami:

// String concatenation
string imie = "Jan";
string nazwisko = "Kowalski";
string pełneImie = imie + " " + nazwisko;  // "Jan Kowalski"

// Mieszanie typów
int liczbaInt = 10;
double liczbaDouble = 3.5;
double wynik = liczbaInt + liczbaDouble;  // 13.5 (int → double)

// Char jako liczby
char litera = 'A';
int kodASCII = litera + 0;  // 65
char nastepnaLitera = (char)(litera + 1);  // 'B'

Console.WriteLine($"Kod ASCII 'A': {kodASCII}");
Console.WriteLine($"Następna litera: {nastepnaLitera}");

2. Operatory przypisania

Służą do przypisywania wartości do zmiennych.

Podstawowy operator przypisania:

int x;          // deklaracja
x = 10;         // przypisanie wartości 10 do x
int y = 20;     // deklaracja z jednoczesnym przypisaniem

Złożone operatory przypisania:

OperatorDługa formaKrótka formaOpis
+=x = x + yx += yDodaje i przypisuje
-=x = x - yx -= yOdejmuje i przypisuje
*=x = x * yx *= yMnoży i przypisuje
/=x = x / yx /= yDzieli i przypisuje
%=x = x % yx %= yModulo i przypisuje
int liczba = 10;

liczba += 5;    // liczba = liczba + 5  → 15
liczba -= 3;    // liczba = liczba - 3  → 12
liczba *= 2;    // liczba = liczba * 2  → 24
liczba /= 4;    // liczba = liczba / 4  → 6
liczba %= 4;    // liczba = liczba % 4  → 2

Console.WriteLine($"Końcowy wynik: {liczba}");

// Przykłady z innymi typami
string tekst = "Hello";
tekst += " World";  // "Hello World"

double saldo = 1000.0;
saldo *= 1.05;      // Podwyżka o 5% → 1050.0

3. Operatory porównania (relacyjne)

Porównują dwie wartości i zwracają wynik typu bool (true lub false).

Tabela operatorów porównania:

OperatorNazwaOpisPrzykład
==RównośćSprawdza czy operandy są równe5 == 5true
!=NierównośćSprawdza czy operandy są różne5 != 3true
>Większe niżSprawdza czy lewy > prawy5 > 3true
<Mniejsze niżSprawdza czy lewy < prawy3 < 5true
>=Większe lub równeSprawdza czy lewy >= prawy5 >= 5true
<=Mniejsze lub równeSprawdza czy lewy <= prawy3 <= 5true

Przykłady z różnymi typami danych:

// Porównywanie liczb
int a = 10, b = 20;
Console.WriteLine($"{a} == {b}: {a == b}");  // False
Console.WriteLine($"{a} != {b}: {a != b}");  // True
Console.WriteLine($"{a} < {b}: {a < b}");    // True
Console.WriteLine($"{a} > {b}: {a > b}");    // False

// Porównywanie stringów
string imie1 = "Anna";
string imie2 = "ANNA";
string imie3 = "Anna";

Console.WriteLine($"'{imie1}' == '{imie2}': {imie1 == imie2}"); // False (wielkość liter)
Console.WriteLine($"'{imie1}' == '{imie3}': {imie1 == imie3}"); // True

// Porównywanie bez uwzględniania wielkości liter
bool rowneIgnoreCase = string.Equals(imie1, imie2, StringComparison.OrdinalIgnoreCase);
Console.WriteLine($"Równe (ignore case): {rowneIgnoreCase}"); // True

// Porównywanie znaków
char litera1 = 'A';
char litera2 = 'B';
Console.WriteLine($"'{litera1}' < '{litera2}': {litera1 < litera2}"); // True (kod ASCII)

// Porównywanie bool
bool prawda = true;
bool fałsz = false;
Console.WriteLine($"{prawda} == {fałsz}: {prawda == fałsz}"); // False

Pułapki przy porównywaniu:

// Problem z liczbami zmiennoprzecinkowymi
double x = 0.1 + 0.2;
double y = 0.3;
Console.WriteLine($"0.1 + 0.2 == 0.3: {x == y}"); // Może być False!

// Bezpieczne porównywanie double
bool bliskoRowne = Math.Abs(x - y) < 0.0001;
Console.WriteLine($"Bezpieczne porównanie: {bliskoRowne}"); // True

// Porównywanie null
string tekst1 = null;
string tekst2 = "Hello";
// Console.WriteLine(tekst1 == tekst2); // OK - zwróci false
// Console.WriteLine(tekst1.Equals(tekst2)); // BŁĄD! NullReferenceException

4. Operatory logiczne

Służą do łączenia lub modyfikowania wyrażeń logicznych (bool).

Podstawowe operatory logiczne:

OperatorNazwaOpisPrzykład
&&AND (i)True gdy oba operandy są Truetrue && falsefalse
||OR (lub)True gdy przynajmniej jeden operand jest Truetrue || falsetrue
!NOT (nie)Odwraca wartość logiczną!truefalse

Tabela prawdy:

ABA && BA || B!A
truetruetruetruefalse
truefalsefalsetruefalse
falsetruefalsetruetrue
falsefalsefalsefalsetrue

Praktyczne przykłady:

// Sprawdzanie dostępu do systemu
int wiek = 20;
bool maLicencje = true;
bool jestTrzezwy = true;

// Wszystkie warunki muszą być spełnione (AND)
bool mozeProwadzic = (wiek >= 18) && maLicencje && jestTrzezwy;
Console.WriteLine($"Może prowadzić: {mozeProwadzic}"); // True

// Przynajmniej jeden warunek musi być spełniony (OR)
bool jestWeekend = false;
bool jestUrlopze = true;
bool maWolne = jestWeekend || jestUrlopze;
Console.WriteLine($"Ma wolne: {maWolne}"); // True

// Negacja (NOT)
bool padaDeszcz = true;
bool słonecznaDzien = !padaDeszcz;
Console.WriteLine($"Słoneczny dzień: {słonecznaDzien}"); // False

Short-circuit evaluation (ocena z przerwaniem):

int x = 0;
int y = 10;

// && przerywa gdy pierwszy warunek jest false
bool wynik1 = (x != 0) && (y / x > 2);  // Drugi warunek NIE zostanie sprawdzony
Console.WriteLine($"Wynik 1: {wynik1}"); // False, bez błędu dzielenia przez 0

// || przerywa gdy pierwszy warunek jest true  
bool admin = true;
bool superUser = false;
bool maUprawnienia = admin || (superUser && SprawdzUprawnienia());
// SprawdzUprawnienia() NIE zostanie wywołana, bo admin == true

static bool SprawdzUprawnienia()
{
    Console.WriteLine("Sprawdzanie uprawnień..."); // Nie zostanie wyświetlone
    return true;
}

Złożone wyrażenia logiczne:

int temperatura = 22;
bool deszcz = false;
bool wiatr = true;
bool weekend = true;

// Skomplikowany warunek na wyjście na zewnątrz
bool dobryDzien = (temperatura > 15 && temperatura < 30) &&  // Odpowiednia temperatura
                  !deszcz &&                                  // Nie pada
                  (!wiatr || temperatura > 20) &&           // Nie wieje LUB jest ciepło
                  weekend;                                    // Jest weekend

Console.WriteLine($"Dobry dzień na spacer: {dobryDzien}");

// Logika De Morgana - równoważne wyrażenia:
bool a = true, b = false;

// !(a && b) == (!a || !b)
bool wyrażenie1 = !(a && b);    // !(true && false) = !false = true
bool wyrażenie2 = !a || !b;     // !true || !false = false || true = true
Console.WriteLine($"De Morgan 1: {wyrażenie1 == wyrażenie2}"); // True

// !(a || b) == (!a && !b)  
bool wyrażenie3 = !(a || b);    // !(true || false) = !true = false
bool wyrażenie4 = !a && !b;     // !true && !false = false && true = false
Console.WriteLine($"De Morgan 2: {wyrażenie3 == wyrażenie4}"); // True

5. Operatory warunkowe

Operator warunkowy (ternary operator) ? :

Składnia: warunek ? wartość_jeśli_true : wartość_jeśli_false

int wiek = 17;

// Tradycyjne if-else
string status1;
if (wiek >= 18)
    status1 = "pełnoletni";
else
    status1 = "niepełnoletni";

// Operator warunkowy - krócej
string status2 = (wiek >= 18) ? "pełnoletni" : "niepełnoletni";

Console.WriteLine($"Status: {status2}");

// Inne przykłady
int a = 10, b = 20;
int maksimum = (a > b) ? a : b;  // zwraca większą liczbę

string pogoda = "słonecznie";
string ubiór = (pogoda == "deszcz") ? "kurtka przeciwdeszczowa" : "lekka koszulka";

// Zagnieżdżone operatory warunkowe (ostrożnie z czytelnością!)
int punkty = 85;
string ocena = (punkty >= 90) ? "A" : 
               (punkty >= 80) ? "B" :
               (punkty >= 70) ? "C" : "F";
Console.WriteLine($"Ocena: {ocena}");

Null-coalescing operator ??

string tekst = null;
string domyślnyTekst = "Wartość domyślna";

// Tradycyjnie
string wynik1 = (tekst != null) ? tekst : domyślnyTekst;

// Z operatorem ?? - krócej
string wynik2 = tekst ?? domyślnyTekst;

Console.WriteLine($"Wynik: {wynik2}"); // "Wartość domyślna"

// Przykład praktyczny
string nazwaSerwera = GetServerName() ?? "localhost";
int portSerwera = GetServerPort() ?? 8080;

static string GetServerName() => null; // Symulacja braku konfiguracji
static int? GetServerPort() => null;   // Zwraca nullable int

Null-conditional operator ?. (C# 6.0+)

string tekst = null;
Person osoba = null;

// Tradycyjne sprawdzanie null
int długość1 = (tekst != null) ? tekst.Length : 0;

// Z operatorem ?. - bezpieczniej
int? długość2 = tekst?.Length;  // null jeśli tekst == null
int długość3 = tekst?.Length ?? 0;  // 0 jeśli tekst == null

// Łańcuchowe wywołania
string miasto = osoba?.Address?.City ?? "Nieznane miasto";
Console.WriteLine($"Miasto: {miasto}");

public class Person
{
    public Address Address { get; set; }
}

public class Address  
{
    public string City { get; set; }
}

6. Operatory typu

Operator is – sprawdzanie typu

object wartość = "Hello World";

// Tradycyjne sprawdzanie typu
if (wartość is string)
{
    string tekst = (string)wartość;
    Console.WriteLine($"To string o długości: {tekst.Length}");
}

// Pattern matching (C# 7.0+) - jednocześnie sprawdza i rzutuje
if (wartość is string str)
{
    Console.WriteLine($"To string: {str}");
}

// Z dodatkowymi warunkami
if (wartość is string s && s.Length > 5)
{
    Console.WriteLine($"To długi string: {s}");
}

// Przykłady z różnymi typami
object[] różneWartości = { 42, "tekst", 3.14, true };

foreach (object item in różneWartości)
{
    switch (item)
    {
        case int liczba:
            Console.WriteLine($"Liczba całkowita: {liczba}");
            break;
        case string tekst:
            Console.WriteLine($"Tekst: {tekst}");
            break;
        case double rzeczywista:
            Console.WriteLine($"Liczba rzeczywista: {rzeczywista}");
            break;
        case bool logiczna:
            Console.WriteLine($"Wartość logiczna: {logiczna}");
            break;
        default:
            Console.WriteLine($"Nieznany typ: {item}");
            break;
    }
}

Operator as – bezpieczne rzutowanie

object wartość = "Hello";

// Tradycyjne rzutowanie - może rzucić wyjątek
string tekst1 = (string)wartość;  // OK

object liczba = 42;
// string tekst2 = (string)liczba;  // InvalidCastException!

// Bezpieczne rzutowanie z 'as' - zwraca null przy niepowodzeniu
string tekst3 = liczba as string;  // null (bez wyjątku)

if (tekst3 != null)
{
    Console.WriteLine($"Udało się: {tekst3}");
}
else
{
    Console.WriteLine("Nie udało się rzutować na string");
}

// Praktyczny przykład
object[] obiekty = { "Hello", 123, "World", 45.6 };

foreach (object obj in obiekty)
{
    string jakisTekst = obj as string;
    if (jakisTekst != null)
    {
        Console.WriteLine($"String: {jakisTekst}");
    }
}

Operator typeof – informacje o typie

// Pobieranie informacji o typie
Type typInt = typeof(int);
Type typString = typeof(string);
Type typDateTime = typeof(DateTime);

Console.WriteLine($"Nazwa typu int: {typInt.Name}");
Console.WriteLine($"Pełna nazwa: {typInt.FullName}");

// Porównywanie typów
object wartość = 42;
Type typWartości = wartość.GetType();

if (typWartości == typeof(int))
{
    Console.WriteLine("To jest int");
}

// Sprawdzanie właściwości typu
if (typInt.IsValueType)
    Console.WriteLine("int to typ wartościowy");
    
if (!typString.IsValueType)
    Console.WriteLine("string to typ referencyjny");

7. Operatory bitowe

Operują na poziomie pojedynczych bitów w reprezentacji binarnej liczb.

Podstawowe operatory bitowe:

OperatorNazwaOpis
&Bitowe ANDBit wynikowy = 1 gdy oba bity = 1
|Bitowe ORBit wynikowy = 1 gdy przynajmniej jeden bit = 1
^Bitowe XORBit wynikowy = 1 gdy bity są różne
~Bitowe NOTOdwraca wszystkie bity
<<Przesunięcie w lewoPrzesuwa bity w lewo
>>Przesunięcie w prawoPrzesuwa bity w prawo
// Przykłady operacji bitowych
byte a = 12;  // 00001100 w binarnym
byte b = 10;  // 00001010 w binarnym

byte andWynik = (byte)(a & b);  // 00001000 = 8
byte orWynik = (byte)(a | b);   // 00001110 = 14  
byte xorWynik = (byte)(a ^ b);  // 00000110 = 6
byte notWynik = (byte)(~a);     // 11110011 = 243 (w byte)

Console.WriteLine($"a & b = {andWynik}");   // 8
Console.WriteLine($"a | b = {orWynik}");    // 14
Console.WriteLine($"a ^ b = {xorWynik}");   // 6
Console.WriteLine($"~a = {notWynik}");      // 243

// Przesunięcia bitowe
int liczba = 5;  // 00000101
int przesuniętaLewo = liczba << 2;   // 00010100 = 20 (5 * 2^2)
int przesuniętaPrawo = liczba >> 1;  // 00000010 = 2 (5 / 2^1)

Console.WriteLine($"{liczba} << 2 = {przesuniętaLewo}");  // 20
Console.WriteLine($"{liczba} >> 1 = {przesuniętaPrawo}"); // 2

Praktyczne zastosowania operatorów bitowych:

// Flagi (enum z atrybutem [Flags])
[Flags]
enum FileAccess
{
    None = 0,      // 000
    Read = 1,      // 001  
    Write = 2,     // 010
    Execute = 4    // 100
}

// Łączenie uprawnień
FileAccess uprawnienia = FileAccess.Read | FileAccess.Write;  // 011 = 3
Console.WriteLine($"Uprawnienia: {uprawnienia}");

// Sprawdzanie czy ma określone uprawnienie
bool czyMozeOdczytywac = (uprawnienia & FileAccess.Read) == FileAccess.Read;
Console.WriteLine($"Może odczytywać: {czyMozeOdczytywac}");

// Usuwanie uprawnienia
uprawnienia = uprawnienia & ~FileAccess.Write;  // Usuń Write
Console.WriteLine($"Po usunięciu Write: {uprawnienia}");

// Przełączanie uprawnienia (toggle)
uprawnienia = uprawnienia ^ FileAccess.Execute;  // Dodaj/usuń Execute
Console.WriteLine($"Po przełączeniu Execute: {uprawnienia}");

8. Inne przydatne operatory

Operator sizeof

// Rozmiar typów wartościowych w bajtach
Console.WriteLine($"sizeof(bool): {sizeof(bool)} bajtów");     // 1
Console.WriteLine($"sizeof(byte): {sizeof(byte)} bajtów");     // 1
Console.WriteLine($"sizeof(int): {sizeof(int)} bajtów");       // 4
Console.WriteLine($"sizeof(long): {sizeof(long)} bajtów");     // 8
Console.WriteLine($"sizeof(float): {sizeof(float)} bajtów");   // 4
Console.WriteLine($"sizeof(double): {sizeof(double)} bajtów"); // 8
Console.WriteLine($"sizeof(decimal): {sizeof(decimal)} bajtów"); // 16

Operator nameof (C# 6.0+)

string zmiennaName = "test";
int liczba = 42;

// Pobiera nazwę zmiennej/metody/typu jako string
Console.WriteLine(nameof(zmiennaName)); // "zmiennaName"  
Console.WriteLine(nameof(liczba));      // "liczba"
Console.WriteLine(nameof(Console));     // "Console"
Console.WriteLine(nameof(Console.WriteLine)); // "WriteLine"

// Przydatne w logowaniu i debugowaniu
void LogError(string parameterName)
{
    Console.WriteLine($"Błąd w parametrze: {parameterName}");
}

// Użycie
LogError(nameof(zmiennaName)); // "Błąd w parametrze: zmiennaName"

Kolejność wykonywania operatorów (precedencja)

Gdy w wyrażeniu występuje kilka operatorów, ważna jest kolejność ich wykonywania:

Tabela precedencji (od najwyższej do najniższej):

  1. Operatory podstawowe: (), [], ., ?.
  2. Operatory unarne: +, -, !, ~, ++, --, (typ)
  3. Mnożenie, dzielenie: *, /, %
  4. Dodawanie, odejmowanie: +, -
  5. Przesunięcia: <<, >>
  6. Porównania: <, >, <=, >=, is, as
  7. Równość: ==, !=
  8. Bitowe AND: &
  9. Bitowe XOR: ^
  10. Bitowe OR: |
  11. Logiczne AND: &&
  12. Logiczne OR: ||
  13. Operatory warunkowe: ?:, ??
  14. Przypisanie: =, +=, -=, *=, /=, %=

Przykłady precedencji:

// Bez nawiasów - precedencja naturalna
int wynik1 = 2 + 3 * 4;      // 2 + (3 * 4) = 14
int wynik2 = 10 / 2 + 3;     // (10 / 2) + 3 = 8  
bool wynik3 = 5 > 3 && 2 < 4; // (5 > 3) && (2 < 4) = true

// Z nawiasami - wymuszona kolejność
int wynik4 = (2 + 3) * 4;    // (2 + 3) * 4 = 20
int wynik5 = 10 / (2 + 3);   // 10 / (2 + 3) = 2

Console.WriteLine($"2 + 3 * 4 = {wynik1}");        // 14
Console.WriteLine($"(2 + 3) * 4 = {wynik4}");      // 20