·
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
Paweł Łukasiewicz: programista blogger
Paweł Łukasiewicz
2026-04-01
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#! 🚀✨