Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Udostępnij Udostępnij Kontakt
Wprowadzenie

Do tej pory pisaliśmy kod który wykonywał się linia po linii, od góry do dołu. Ale prawdziwe programy muszą podejmować decyzje ("jeśli użytkownik jest zalogowany, pokaż dashboard") i powtarzać operacje ("dla każdego produktu, oblicz cenę"). Do tego służy kontrola przepływu programu!

W tym wpisie nauczysz się if/else z pattern matching, rewolucyjnych switch expressions (C# 8+), wszystkich rodzajów pattern matching (C# 7-14), oraz pętli - for, foreach, while. Zapomnij o starym switch statement - poznasz nowoczesny sposób!

🔍 Czym jest kontrola przepływu?

Kontrola przepływu to sposób w jaki decydujesz co program ma robić:

  • Instrukcje warunkowe - if/else, switch - "jeśli... to..."
  • Pętle - for, foreach, while - "powtarzaj dopóki..."
  • Jump statements - break, continue, return - "przerwij/kontynuuj/wyjdź"
if/else - instrukcje warunkowe

Podstawowa składnia

int age = 20;

// Prosty if
if (age >= 18)
{
    Console.WriteLine("Pełnoletni");
}

// if-else
if (age >= 18)
{
    Console.WriteLine("Pełnoletni");
}
else
{
    Console.WriteLine("Niepełnoletni");
}

// if-else if-else
if (age < 13)
{
    Console.WriteLine("Dziecko");
}
else if (age < 18)
{
    Console.WriteLine("Nastolatek");
}
else if (age < 65)
{
    Console.WriteLine("Dorosły");
}
else
{
    Console.WriteLine("Senior");
}

// Bez nawiasów klamrowych (tylko dla JEDNEJ instrukcji)
if (age >= 18)
    Console.WriteLine("Pełnoletni");  // tylko jedna linia - OK
else
    Console.WriteLine("Niepełnoletni");
⚠️ Zawsze używaj {} nawet dla jednej linii!
// ❌ Bez nawiasów - podatne na błędy!
if (age >= 18)
    Console.WriteLine("Pełnoletni");
    Console.WriteLine("Może głosować");  // To ZAWSZE się wykona! (nie jest w if)

// ✅ Z nawiasami - bezpieczne
if (age >= 18)
{
    Console.WriteLine("Pełnoletni");
    Console.WriteLine("Może głosować");  // Oba w if
}

99% C# devs używa ZAWSZE nawiasów. To zapobiega błędom i jest bardziej czytelne. 👍

Operatory logiczne w warunkach

int age = 25;
bool isStudent = true;
string country = "Poland";

// && - AND (i)
if (age >= 18 && age <= 65)
{
    Console.WriteLine("Wiek produkcyjny");
}

// || - OR (lub)
if (age < 18 || age > 65)
{
    Console.WriteLine("Poza wiekiem produkcyjnym");
}

// ! - NOT (negacja)
if (!isStudent)
{
    Console.WriteLine("Nie jest studentem");
}

// Kombinacje
if (age >= 18 && (isStudent || country == "Poland"))
{
    Console.WriteLine("Może otrzymać zniżkę");
}

// Porównanie stringów - case insensitive
if (country.Equals("poland", StringComparison.OrdinalIgnoreCase))
{
    Console.WriteLine("Polska!");
}

Null checking - różne sposoby

string? name = GetUserName();  // może być null

// 1. Klasyczny null check
if (name != null)
{
    Console.WriteLine(name.ToUpper());
}

// 2. Null-conditional operator (C# 6+)
Console.WriteLine(name?.ToUpper());  // null jeśli name jest null

// 3. is not null (C# 9+) - czytelniejsze!
if (name is not null)
{
    Console.WriteLine(name.ToUpper());
}

// 4. Pattern matching (C# 7+) - z deklaracją zmiennej
if (name is string validName)
{
    Console.WriteLine(validName.ToUpper());  // validName to string (nie nullable)
}

// 5. IsNullOrWhiteSpace dla stringów
if (!string.IsNullOrWhiteSpace(name))
{
    Console.WriteLine(name.ToUpper());
}
❌ Stary sposób (2015)
string name = GetName();

if (name != null)
{
    if (name.Length > 0)
    {
        Console.WriteLine(name);
    }
}

// Zagnieżdżone if'y - brzydkie!
✅ Nowoczesny sposób (2026)
string? name = GetName();

// Pattern matching + is
if (name is { Length: > 0 })
{
    Console.WriteLine(name);
}

// Lub guard clause (więcej o tym później)
if (name is null or { Length: 0 }) return;
Console.WriteLine(name);

Ternary operator - skrócony if

int age = 20;

// Pełny if-else
string status;
if (age >= 18)
{
    status = "Dorosły";
}
else
{
    status = "Niepełnoletni";
}

// Ternary operator - condition ? valueIfTrue : valueIfFalse
string status2 = age >= 18 ? "Dorosły" : "Niepełnoletni";

// Zagnieżdżony ternary (NIE NADUŻYWAJ!)
string category = age < 13 ? "Dziecko" 
                : age < 18 ? "Nastolatek" 
                : age < 65 ? "Dorosły" 
                : "Senior";

// Użycie w wyrażeniach
int discount = isStudent ? 20 : 0;
Console.WriteLine($"Zniżka: {discount}%");

// W LINQ
var adults = users.Where(u => u.Age >= 18 ? true : false);  // ❌ niepotrzebne!
var adults2 = users.Where(u => u.Age >= 18);  // ✅ wystarczy warunek
Pattern Matching - potęga rozpoznawania wzorców

Type pattern - sprawdzanie typu

object obj = "Hello";

// Type pattern - is Type variableName
if (obj is string text)
{
    Console.WriteLine($"Długość: {text.Length}");
    // 'text' jest typu string (nie object)
}

// Działa z wieloma typami
object value = 42;

if (value is int number)
{
    Console.WriteLine($"Liczba: {number}");
}
else if (value is string str)
{
    Console.WriteLine($"String: {str}");
}
else if (value is bool flag)
{
    Console.WriteLine($"Bool: {flag}");
}

// Negative pattern - is not (C# 9+)
if (obj is not string)
{
    Console.WriteLine("To nie jest string");
}

Constant pattern - stałe wartości

int number = 42;

// Constant pattern
if (number is 42)
{
    Console.WriteLine("Answer to everything!");
}

// Null pattern
string? name = null;
if (name is null)
{
    Console.WriteLine("Name is null");
}

// Not null (C# 9+)
if (name is not null)
{
    Console.WriteLine($"Name: {name}");
}

Property pattern - sprawdzanie właściwości

record Person(string Name, int Age, string City);

Person person = new("Jan", 30, "Warszawa");

// Property pattern - { PropertyName: pattern }
if (person is { Age: 30 })
{
    Console.WriteLine("Ma 30 lat");
}

// Wiele właściwości
if (person is { Age: 30, City: "Warszawa" })
{
    Console.WriteLine("30-latek z Warszawy");
}

// Z relational patterns (C# 9+)
if (person is { Age: >= 18 and < 65 })
{
    Console.WriteLine("Wiek produkcyjny");
}

// Nested properties
record Address(string City, string Street);
record Employee(string Name, Address Address);

Employee emp = new("Jan", new("Warszawa", "Marszałkowska"));

if (emp is { Address.City: "Warszawa" })
{
    Console.WriteLine("Pracownik z Warszawy");
}

Relational patterns - porównania (C# 9+)

int age = 25;

// Relational patterns: <,>, <=, >=
if (age is >= 18)
{
    Console.WriteLine("Dorosły");
}

if (age is > 0 and < 18)
{
    Console.WriteLine("Niepełnoletni");
}

// Kombinacje z logical patterns
if (age is >= 18 and < 65)
{
    Console.WriteLine("Wiek produkcyjny");
}

if (age is < 13 or > 65)
{
    Console.WriteLine("Zniżka seniorska/dziecięca");
}

// Not pattern
if (age is not 0)
{
    Console.WriteLine("Wiek określony");
}

Logical patterns - and, or, not (C# 9+)

int number = 42;

// and
if (number is > 0 and < 100)
{
    Console.WriteLine("Liczba od 1 do 99");
}

// or
if (number is 0 or 100)
{
    Console.WriteLine("Graniczna wartość");
}

// not
if (number is not 0)
{
    Console.WriteLine("Niezerowa");
}

// Kombinacje
string? text = "Hello";
if (text is not null and { Length: > 0 })
{
    Console.WriteLine("Niepusty string");
}

// Zaawansowane
int value = 50;
if (value is (> 0 and < 25) or (> 75 and < 100))
{
    Console.WriteLine("Pierwszy lub ostatni kwartyl");
}

List patterns - wzorce list (C# 11+)

int[] numbers = { 1, 2, 3, 4, 5 };

// List pattern - [element1, element2, ...]
if (numbers is [1, 2, 3, 4, 5])
{
    Console.WriteLine("Dokładnie te liczby");
}

// Discard pattern - _ (ignoruj element)
if (numbers is [1, _, 3, _, 5])
{
    Console.WriteLine("1, coś, 3, coś, 5");
}

// Slice pattern - .. (reszta elementów)
if (numbers is [1, 2, ..])
{
    Console.WriteLine("Zaczyna się od 1, 2");
}

if (numbers is [.., 4, 5])
{
    Console.WriteLine("Kończy się na 4, 5");
}

if (numbers is [1, .., 5])
{
    Console.WriteLine("Pierwszy = 1, ostatni = 5");
}

// Var pattern - złap resztę
if (numbers is [1, 2, .. var rest])
{
    Console.WriteLine($"Pierwsze 2: 1, 2. Reszta: {string.Join(", ", rest)}");
    // rest = { 3, 4, 5 }
}

// Length pattern
if (numbers is { Length: 5 })
{
    Console.WriteLine("Tablica ma 5 elementów");
}
💡 Praktyczne przykłady pattern matching
// Przykład 1: Walidacja użytkownika
record User(string Name, int Age, bool IsActive);

User user = new("Jan", 25, true);

if (user is { Age: >= 18, IsActive: true })
{
    Console.WriteLine("Użytkownik może się zalogować");
}

// Przykład 2: Parsing różnych typów
object value = GetValue();

string description = value switch
{
    null => "Null",
    int n when n > 0 => $"Dodatnia liczba: {n}",
    int n when n < 0 => $"Ujemna liczba: {n}",
    int => "Zero",
    string { Length: 0 } => "Pusty string",
    string s => $"String: {s}",
    _ => "Nieznany typ"
};

// Przykład 3: HTTP status codes
int statusCode = 404;

string message = statusCode switch
{
    200 => "OK",
    201 => "Created",
    400 => "Bad Request",
    401 => "Unauthorized",
    403 => "Forbidden",
    404 => "Not Found",
    >= 500 and < 600 => "Server Error",
    _ => "Unknown status"
};
Switch Expression - rewolucja! (C# 8+)

Stary switch statement (NIE UŻYWAJ!)

// ❌ Stary switch statement (C# 1.0)
int dayNumber = 3;
string dayName;

switch (dayNumber)
{
    case 1:
        dayName = "Poniedziałek";
        break;
    case 2:
        dayName = "Wtorek";
        break;
    case 3:
        dayName = "Środa";
        break;
    case 4:
        dayName = "Czwartek";
        break;
    case 5:
        dayName = "Piątek";
        break;
    case 6:
    case 7:
        dayName = "Weekend";
        break;
    default:
        dayName = "Nieprawidłowy dzień";
        break;
}

// Verbose, wymaga break, łatwo o błędy!

Nowy switch expression (UŻYWAJ!)

🎉 NOWOŚĆ C# 8+ - Switch Expression

switch expression to nowoczesna, zwięzła forma switch. Zwraca wartość, nie potrzebuje break, wspiera pattern matching!

// ✅ Nowy switch expression (C# 8+)
int dayNumber = 3;

string dayName = dayNumber switch
{
    1 => "Poniedziałek",
    2 => "Wtorek",
    3 => "Środa",
    4 => "Czwartek",
    5 => "Piątek",
    6 or 7 => "Weekend",  // or pattern!
    _ => "Nieprawidłowy dzień"  // _ = default
};

// Krótkie, czytelne, bezpieczne! ✨
❌ Switch statement (stary)
string size = "M";
decimal price;

switch (size)
{
    case "S":
        price = 9.99m;
        break;
    case "M":
        price = 14.99m;
        break;
    case "L":
        price = 19.99m;
        break;
    default:
        price = 0;
        break;
}

// 13 linii kodu!
✅ Switch expression (nowy)
string size = "M";

decimal price = size switch
{
    "S" => 9.99m,
    "M" => 14.99m,
    "L" => 19.99m,
    _ => 0
};

// 7 linii kodu! ✨

Switch expression z pattern matching

// Type patterns
object value = 42;

string description = value switch
{
    int n => $"Integer: {n}",
    string s => $"String: {s}",
    double d => $"Double: {d}",
    null => "Null",
    _ => "Unknown type"
};

// Property patterns
record Person(string Name, int Age);
Person person = new("Jan", 30);

string category = person switch
{
    { Age: < 13 } => "Dziecko",
    { Age: < 18 } => "Nastolatek",
    { Age: < 65 } => "Dorosły",
    { Age: >= 65 } => "Senior",
    _ => "Unknown"
};

// Relational patterns
int temperature = 25;

string weather = temperature switch
{
    < 0 => "Mróz",
    >= 0 and < 10 => "Zimno",
    >= 10 and < 20 => "Chłodno",
    >= 20 and < 30 => "Ciepło",
    >= 30 => "Gorąco",
    _ => "Unknown"
};

// When guards - dodatkowe warunki
decimal amount = 1500;
bool isPremium = true;

decimal discount = amount switch
{
    < 100 => 0,
    < 500 => 0.05m,
    < 1000 => 0.10m,
    >= 1000 when isPremium => 0.20m,  // when guard!
    >= 1000 => 0.15m,
    _ => 0
};
💡 Praktyczne przykłady switch expression
// Przykład 1: HTTP method handling
string method = "POST";

var response = method switch
{
    "GET" => HandleGet(),
    "POST" => HandlePost(),
    "PUT" => HandlePut(),
    "DELETE" => HandleDelete(),
    _ => throw new NotSupportedException($"Method {method} not supported")
};

// Przykład 2: Obliczanie ceny wysyłki
record Order(decimal TotalAmount, string Country, bool IsExpress);

Order order = new(150, "Poland", false);

decimal shippingCost = order switch
{
    { TotalAmount: > 200 } => 0,  // darmowa wysyłka
    { Country: "Poland", IsExpress: true } => 20,
    { Country: "Poland" } => 10,
    { Country: "Germany" or "Czech", IsExpress: true } => 35,
    { Country: "Germany" or "Czech" } => 25,
    { IsExpress: true } => 50,
    _ => 30
};

// Przykład 3: Kalkulator
string operation = "+";
int a = 10, b = 5;

int result = operation switch
{
    "+" => a + b,
    "-" => a - b,
    "*" => a * b,
    "/" when b != 0 => a / b,
    "/" => throw new DivideByZeroException(),
    _ => throw new ArgumentException("Invalid operation")
};

Tuple patterns w switch

// Switch na tuple (C# 8+)
(int x, int y) point = (3, 4);

string quadrant = point switch
{
    (0, 0) => "Początek układu",
    (> 0, > 0) => "I ćwiartka",
    (< 0, > 0) => "II ćwiartka",
    (< 0, < 0) => "III ćwiartka",
    (> 0, < 0) => "IV ćwiartka",
    (_, 0) => "Oś X",
    (0, _) => "Oś Y",
    _ => "Unknown"
};

// Gra papier-kamień-nożyce
enum Move { Rock, Paper, Scissors }

string Winner(Move player1, Move player2) => (player1, player2) switch
{
    (Move.Rock, Move.Scissors) => "Player 1 wins",
    (Move.Scissors, Move.Paper) => "Player 1 wins",
    (Move.Paper, Move.Rock) => "Player 1 wins",
    (Move.Scissors, Move.Rock) => "Player 2 wins",
    (Move.Paper, Move.Scissors) => "Player 2 wins",
    (Move.Rock, Move.Paper) => "Player 2 wins",
    _ => "Draw"
};
Pętle - powtarzanie operacji

for - pętla z licznikiem

// Podstawowa pętla for
for (int i = 0; i < 5; i++)
{
    Console.WriteLine($"Iteracja {i}");
}
// Output: 0, 1, 2, 3, 4

// Od tyłu
for (int i = 5; i > 0; i--)
{
    Console.WriteLine(i);
}
// Output: 5, 4, 3, 2, 1

// Co drugi element
for (int i = 0; i < 10; i += 2)
{
    Console.WriteLine(i);
}
// Output: 0, 2, 4, 6, 8

// Wiele zmiennych (rzadko używane)
for (int i = 0, j = 10; i < j; i++, j--)
{
    Console.WriteLine($"i={i}, j={j}");
}

// Iteracja po tablicy/liście
int[] numbers = { 10, 20, 30, 40, 50 };
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine($"numbers[{i}] = {numbers[i]}");
}

foreach - pętla po kolekcji

// foreach - najprostsza pętla po kolekcji
int[] numbers = { 10, 20, 30, 40, 50 };

foreach (int number in numbers)
{
    Console.WriteLine(number);
}

// Działa z każdą kolekcją
List names = ["Jan", "Anna", "Piotr"];
foreach (string name in names)
{
    Console.WriteLine(name);
}

// Z var
foreach (var item in numbers)
{
    Console.WriteLine(item);  // item to int (wywnioskowane)
}

// Dictionary - foreach po parach
Dictionary ages = new()
{
    ["Jan"] = 30,
    ["Anna"] = 25
};

foreach (KeyValuePair pair in ages)
{
    Console.WriteLine($"{pair.Key} ma {pair.Value} lat");
}

// C# 7+ - deconstruction
foreach (var (name, age) in ages)
{
    Console.WriteLine($"{name} ma {age} lat");
}
❌ for gdy nie potrzebujesz indeksu
List fruits = ["Jabłko", "Banan", "Gruszka"];

for (int i = 0; i < fruits.Count; i++)
{
    Console.WriteLine(fruits[i]);
}

// Verbose, podatne na błędy (można pomylić Length/Count)
✅ foreach gdy nie potrzebujesz indeksu
List fruits = ["Jabłko", "Banan", "Gruszka"];

foreach (string fruit in fruits)
{
    Console.WriteLine(fruit);
}

// Czytelne, bezpieczne!

while - pętla z warunkiem

// while - wykonuj DOPÓKI warunek jest prawdziwy
int count = 0;
while (count < 5)
{
    Console.WriteLine($"Count: {count}");
    count++;
}

// Nieskończona pętla (wymaga break)
int userInput;
while (true)
{
    Console.Write("Podaj liczbę (0 = koniec): ");
    userInput = int.Parse(Console.ReadLine());
    
    if (userInput == 0)
        break;  // wyjdź z pętli
    
    Console.WriteLine($"Podałeś: {userInput}");
}

// do-while - wykonaj PRZYNAJMNIEJ RAZ, potem sprawdź warunek
int number;
do
{
    Console.Write("Podaj liczbę dodatnią: ");
    number = int.Parse(Console.ReadLine());
} while (number <= 0);

Console.WriteLine($"Dziękuję! Liczba: {number}");

break, continue, return

// break - przerwij pętlę natychmiast
for (int i = 0; i < 10; i++)
{
    if (i == 5)
        break;  // przerwij gdy i == 5
    
    Console.WriteLine(i);
}
// Output: 0, 1, 2, 3, 4 (zatrzymuje się na 5)

// continue - pomiń resztę iteracji, przejdź do następnej
for (int i = 0; i < 10; i++)
{
    if (i % 2 == 0)
        continue;  // pomiń parzyste
    
    Console.WriteLine(i);
}
// Output: 1, 3, 5, 7, 9 (tylko nieparzyste)

// return - wyjdź z metody
void ProcessNumbers(int[] numbers)
{
    foreach (int num in numbers)
    {
        if (num < 0)
        {
            Console.WriteLine("Znaleziono liczbę ujemną!");
            return;  // wyjdź z całej metody
        }
        
        Console.WriteLine(num);
    }
}

// Kombinacja break i continue
List numbers = [1, -5, 3, -2, 8, 0, 10];

foreach (int num in numbers)
{
    if (num == 0)
        break;  // zatrzymaj się na zero
    
    if (num < 0)
        continue;  // pomiń ujemne
    
    Console.WriteLine(num);
}
// Output: 1, 3 (zatrzymuje się na 0)
⚠️ Unikaj zagnieżdżonych break/continue
// ❌ Skomplikowane - trudne do zrozumienia
for (int i = 0; i < 10; i++)
{
    for (int j = 0; j < 10; j++)
    {
        if (i == 5 && j == 5)
            break;  // break tylko z wewnętrznej pętli!
        
        Console.WriteLine($"{i},{j}");
    }
}

// ✅ Lepiej - wydziel do metody
for (int i = 0; i < 10; i++)
{
    if (ProcessRow(i))
        break;  // jasne - break z głównej pętli
}

bool ProcessRow(int row)
{
    for (int j = 0; j < 10; j++)
    {
        if (row == 5 && j == 5)
            return true;  // return = sygnał do break
        
        Console.WriteLine($"{row},{j}");
    }
    return false;
}
Guard Clauses - obronna walidacja

Problem z zagnieżdżonymi if'ami

// ❌ Zagnieżdżone if'y - "Pyramid of Doom"
void ProcessOrder(Order order)
{
    if (order != null)
    {
        if (order.Items.Count > 0)
        {
            if (order.Customer != null)
            {
                if (order.Customer.IsActive)
                {
                    // Właściwa logika tu...
                    CalculateTotal(order);
                }
                else
                {
                    Console.WriteLine("Customer is not active");
                }
            }
            else
            {
                Console.WriteLine("Customer is null");
            }
        }
        else
        {
            Console.WriteLine("No items in order");
        }
    }
    else
    {
        Console.WriteLine("Order is null");
    }
}

// Kod znika w prawo, trudno czytać! 😱

Rozwiązanie - Guard Clauses

// ✅ Guard clauses - early returns
void ProcessOrder(Order? order)
{
    // Walidacja na początku - fail fast!
    if (order is null)
    {
        Console.WriteLine("Order is null");
        return;
    }
    
    if (order.Items.Count == 0)
    {
        Console.WriteLine("No items in order");
        return;
    }
    
    if (order.Customer is null)
    {
        Console.WriteLine("Customer is null");
        return;
    }
    
    if (!order.Customer.IsActive)
    {
        Console.WriteLine("Customer is not active");
        return;
    }
    
    // Właściwa logika - bez zagnieżdżenia!
    CalculateTotal(order);
}

// Płaski kod, łatwy do czytania! ✨
💡 Guard clauses z pattern matching
void ProcessPayment(Payment? payment)
{
    // Guard clauses z pattern matching
    if (payment is null or { Amount: <= 0 })
    {
        throw new ArgumentException("Invalid payment");
    }
    
    if (payment is { Status: PaymentStatus.Completed })
    {
        Console.WriteLine("Payment already processed");
        return;
    }
    
    if (payment is { Account.Balance: < 0 })
    {
        Console.WriteLine("Insufficient funds");
        return;
    }
    
    // Proces płatności
    ProcessPaymentInternal(payment);
}

// Zaawansowane - kombination
record User(string Name, int Age, bool IsActive, string? Email);

void SendEmail(User? user)
{
    // Wszystkie warunki w jednym guard
    if (user is not { IsActive: true, Age: >= 18, Email: not null })
    {
        Console.WriteLine("Cannot send email");
        return;
    }
    
    // user.Email jest na pewno not null tutaj!
    SendEmailTo(user.Email);
}
Zaawansowane wzorce

Expression-bodied members

// Krótkie metody można zapisać jako expression
class Calculator
{
    // Tradycyjna metoda
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    // Expression-bodied method (C# 6+)
    public int Multiply(int a, int b) => a * b;
    
    // Expression-bodied property
    public int CurrentYear => DateTime.Now.Year;
    
    // Expression-bodied readonly property
    private int _value;
    public int DoubleValue => _value * 2;
    
    // Expression-bodied get/set (C# 7+)
    private string _name;
    public string Name
    {
        get => _name;
        set => _name = value?.Trim() ?? "";
    }
}

// Kiedy używać? Dla prostych, jednoliniowych operacji
// ✅ Dobrze:
public bool IsAdult(int age) => age >= 18;
public string FullName => $"{FirstName} {LastName}";

// ❌ Źle (za długie):
public decimal CalculateDiscount(Order order) => 
    order.Items.Sum(i => i.Price) > 1000 ? 
    order.Customer.IsPremium ? 0.25m : 0.15m : 0.05m;
// To powinno być normalną metodą z {}

Null-coalescing assignment ??=

// ??= - przypisz jeśli null (C# 8+)
List? items = null;

// Stary sposób
if (items == null)
{
    items = new List();
}

// Nowy sposób
items ??= new List();
// "Jeśli items jest null, przypisz new List()"

// Praktyczne użycie - lazy initialization
class UserService
{
    private List? _cachedUsers;
    
    public List GetUsers()
    {
        // Załaduj tylko raz
        _cachedUsers ??= LoadUsersFromDatabase();
        return _cachedUsers;
    }
}

// Krótsze warianty
string? name = null;
name ??= "Default";  // name = "Default"

name = "John";
name ??= "Default";  // name nadal = "John" (nie null)

Throw expressions (C# 7+)

// throw można użyć jako wyrażenia (nie tylko instrukcji)

// W ternary operator
string GetName(string? input) => 
    input ?? throw new ArgumentNullException(nameof(input));

// W null-coalescing
string name = userName ?? throw new InvalidOperationException("User not logged in");

// W switch expression
string GetStatusMessage(int code) => code switch
{
    200 => "OK",
    404 => "Not Found",
    _ => throw new ArgumentException($"Unknown code: {code}")
};

// W expression-bodied member
class Person
{
    private string _name;
    public string Name
    {
        get => _name;
        set => _name = !string.IsNullOrEmpty(value) 
            ? value 
            : throw new ArgumentException("Name cannot be empty");
    }
}
Podsumowanie

Ogromny wpis o kontroli przepływu! Nauczyłeś się wszystkiego o podejmowaniu decyzji i pętlach w C# 14:

  • if/else – podstawy, operatory logiczne, ternary operator
  • Pattern matching – type, constant, property, relational, logical patterns
  • List patterns (C# 11) – wzorce dla list/tablic
  • 🔥 Switch expression (C# 8+) – nowoczesna alternatywa dla switch statement
  • Switch z patterns – property patterns, when guards, tuple patterns
  • Pętle – for, foreach, while, do-while
  • break, continue, return – kontrola przepływu w pętlach
  • Guard clauses – early returns, unikanie pyramid of doom
  • Expression-bodied members – krótsza składnia dla prostych metod
  • ??= operator – null-coalescing assignment (C# 8+)
  • Throw expressions – throw jako wyrażenie (C# 7+)

W kolejnym wpisie zagłębimy się w metody i funkcje w nowoczesnym stylu – local functions, lambdy, expression-bodied, async/await podstawy!

Zadanie dla Ciebie 🎯

Stwórz program "Grade Calculator" używając switch expressions:

  1. Użytkownik podaje punkty (0-100)
  2. Program oblicza ocenę używając switch expression:
    • 90-100: A (Celujący)
    • 80-89: B (Bardzo dobry)
    • 70-79: C (Dobry)
    • 60-69: D (Dostateczny)
    • < 60: F (Niedostateczny)
  3. Dla oceny A lub B wyświetl "Gratulacje!"
  4. Dla F wyświetl "Musisz poprawić"
  5. Użyj switch expression z relational patterns!
// Przykładowe działanie:
Podaj punkty (0-100): 85
Ocena: B (Bardzo dobry)
Gratulacje!
🎯 BONUS: FizzBuzz z Pattern Matching

Klasyczny problem FizzBuzz, ale z nowoczesnym C# 14!

Zasady:

  1. Wypisz liczby od 1 do 100
  2. Dla liczb podzielnych przez 3: wypisz "Fizz"
  3. Dla liczb podzielnych przez 5: wypisz "Buzz"
  4. Dla liczb podzielnych przez 3 i 5: wypisz "FizzBuzz"
  5. W pozostałych przypadkach: wypisz liczbę

Wymagania techniczne:

  • ✅ Użyj foreach i range (1..100)
  • ✅ Użyj switch expression z tuple pattern
  • ✅ Stwórz metodę GetFizzBuzz(int number) z expression body
  • ✅ Użyj pattern matching do sprawdzania podzielności

Podpowiedź:

string GetFizzBuzz(int n) => (n % 3, n % 5) switch
{
    (0, 0) => "FizzBuzz",
    (0, _) => "Fizz",
    (_, 0) => "Buzz",
    _ => n.ToString()
};

foreach (var i in Enumerable.Range(1, 100))
{
    Console.WriteLine(GetFizzBuzz(i));
}

Rozszerzenie (hard mode!):

Dodaj więcej reguł:

  • Liczby podzielne przez 7: "Bazz"
  • Kombinacje: FizzBazz (3 i 7), BuzzBazz (5 i 7), FizzBuzzBazz (3, 5 i 7)
  • Liczby pierwsze: wypisz "Prime" zamiast liczby

To świetne ćwiczenie pattern matching i switch expressions! Pokażesz że opanowałeś nowoczesny C#! 🚀✨