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

W 2015 roku pisałeś metody w jeden sposób. W 2026 roku masz rewolucyjne nowe możliwości! Expression-bodied members (C# 6), local functions (C# 7), static lambdas (C# 9), parameter modifiers w lambdach (C# 14), i async/await który zmienił sposób pisania kodu asynchronicznego.

W tym wpisie zobaczysz ewolucję metod i funkcji - co się zmieniło od 2015 do 2026, jakie nowe możliwości daje C# 14, i jak pisać nowoczesny, zwięzły kod który jest czytelniejszy niż kiedykolwiek.

📅 Timeline - ewolucja metod w C#
  • C# 1.0-5.0 (2015) - klasyczne metody, lambda expressions
  • C# 6 (2015) - Expression-bodied members => value
  • C# 7 (2017) - Local functions, więcej expression-bodied
  • C# 9 (2020) - Static lambdas, lambda attributes
  • C# 10 (2021) - Lambda improvements (type inference)
  • C# 11 (2022) - Static abstract members w interfejsach
  • C# 12 (2023) - Primary constructors
  • C# 14 (2026) - 🔥 Parameter modifiers w lambdach (ref, out, in, params)
Expression-bodied members - rewolucja zwięzłości (C# 6+)

C# 6 (2015) - początek rewolucji

❌ Przed C# 6 (2015)
class Person
{
    private string _firstName;
    private string _lastName;
    
    public string GetFullName()
    {
        return _firstName + " " + _lastName;
    }
    
    public string FullName
    {
        get { return _firstName + " " + _lastName; }
    }
}

// Verbose, dużo {} i return
✅ C# 6+ - expression-bodied
class Person
{
    private string _firstName;
    private string _lastName;
    
    // Expression-bodied method
    public string GetFullName() => 
        $"{_firstName} {_lastName}";
    
    // Expression-bodied property
    public string FullName => 
        $"{_firstName} {_lastName}";
}

// Zwięźle, czytelnie! ✨

C# 7 - rozszerzenie na więcej członków

class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
    
    // C# 6 - methods i read-only properties
    public double Area => Width * Height;
    public double Perimeter() => 2 * (Width + Height);
    
    // C# 7 - constructors!
    public Rectangle(double width, double height) => 
        (Width, Height) = (width, height);
    
    // C# 7 - destructors (finalizers)
    ~Rectangle() => Console.WriteLine("Rectangle destroyed");
    
    // C# 7 - getters i setters!
    private double _diagonal;
    public double Diagonal
    {
        get => Math.Sqrt(Width * Width + Height * Height);
        set => _diagonal = value;
    }
}
🎉 C# 7 rozszerza expression-bodied na:
  • ✅ Constructors (konstruktory)
  • ✅ Destructors (finalizery)
  • ✅ Property getters i setters
  • ✅ Indexers

Praktyczne przykłady - kiedy używać

record Product(string Name, decimal Price)
{
    // ✅ DOBRZE - proste obliczenia
    public decimal PriceWithTax => Price * 1.23m;
    public bool IsExpensive => Price > 1000;
    public string DisplayName => $"{Name} (${Price})";
    
    // ✅ DOBRZE - proste metody
    public decimal CalculateDiscount(decimal percentage) => Price * (percentage / 100);
    public bool IsInPriceRange(decimal min, decimal max) => Price >= min && Price <= max;
    
    // ❌ ŹLE - za skomplikowane, użyj normalnego bloku {}
    public string GetDescription() => 
        $"Product: {Name}, Price: {Price}, Tax: {PriceWithTax - Price}, " +
        $"Category: {(IsExpensive ? "Premium" : "Standard")}, " +
        $"Available: {(Price > 0 ? "Yes" : "No")}";
    // To powinno być w {} - nieczytelne!
}
🔍 Zasada: Expression-bodied gdy mieści się w 1-2 linii

Jeśli wyrażenie jest krótkie i czytelne (1-2 linie, max ~80 znaków) - używaj =>. Jeśli dłuższe lub wymaga wielu operacji - użyj normalnego bloku { }.

🔥 Local Functions - funkcje wewnętrzne (C# 7+)

Problem przed C# 7

// ❌ Przed C# 7 - private methods zaśmiecają klasę
class Calculator
{
    public int ProcessData(int[] numbers)
    {
        var filtered = FilterNegatives(numbers);  // helper używany tylko tu
        var doubled = DoubleValues(filtered);     // helper używany tylko tu
        return SumValues(doubled);                // helper używany tylko tu
    }
    
    // Te 3 metody używane TYLKO w ProcessData, ale są w całej klasie
    private int[] FilterNegatives(int[] nums) => nums.Where(n => n >= 0).ToArray();
    private int[] DoubleValues(int[] nums) => nums.Select(n => n * 2).ToArray();
    private int SumValues(int[] nums) => nums.Sum();
}

// Zaśmieca klasę metodami używanymi tylko raz!

Rozwiązanie - Local Functions!

🎉 C# 7 - Local Functions

Funkcje zdefiniowane wewnątrz innych metod. Widoczne tylko w tej metodzie!

// ✅ C# 7+ - local functions
class Calculator
{
    public int ProcessData(int[] numbers)
    {
        // Local functions - zdefiniowane wewnątrz metody
        int[] FilterNegatives(int[] nums) => nums.Where(n => n >= 0).ToArray();
        int[] DoubleValues(int[] nums) => nums.Select(n => n * 2).ToArray();
        int SumValues(int[] nums) => nums.Sum();
        
        // Użycie
        var filtered = FilterNegatives(numbers);
        var doubled = DoubleValues(filtered);
        return SumValues(doubled);
    }
    
    // Klasa jest czysta - brak pomocniczych metod! ✨
}

// FilterNegatives() nie jest widoczna poza ProcessData

C# 8 - Static Local Functions

int Calculate(int x, int y)
{
    int localVar = 10;
    
    // Local function - MA dostęp do zmiennych z metody nadrzędnej
    int Add()
    {
        return x + y + localVar;  // ✅ może użyć x, y, localVar
    }
    
    // C# 8 - Static local function - NIE MA dostępu do zmiennych
    static int Multiply(int a, int b)
    {
        return a * b;
        // return x + y;  // ❌ BŁĄD! static nie widzi x, y
    }
    
    return Add() + Multiply(x, y);
}
🔍 Kiedy używać static local functions?

static wymusza że funkcja NIE przechwytuje zmiennych z otoczenia (closure). To:

  • Zapobiega przypadkowemu użyciu zmiennych z zewnątrz
  • Lepsza performance (brak alokacji dla closure)
  • Jasno komunikuje intencję - "ta funkcja jest niezależna"

Praktyczne przykłady local functions

// Przykład 1: Walidacja z wieloma krokami
(bool isValid, string error) ValidateUser(string username, string password)
{
    // Local validation helpers
    bool IsValidUsername()
    {
        if (string.IsNullOrWhiteSpace(username)) return false;
        if (username.Length < 3) return false;
        return true;
    }
    
    bool IsValidPassword()
    {
        if (string.IsNullOrWhiteSpace(password)) return false;
        if (password.Length < 6) return false;
        return true;
    }
    
    // Użycie
    if (!IsValidUsername()) return (false, "Invalid username");
    if (!IsValidPassword()) return (false, "Invalid password");
    
    return (true, "");
}

// Przykład 2: Iterator z walidacją
IEnumerable<int> GetSquares(int count)
{
    // Walidacja PRZED iteratorem
    if (count < 0)
        throw new ArgumentException("count must be >= 0");
    
    // Local iterator function
    return Iterator();
    
    IEnumerable Iterator()
    {
        for (int i = 0; i < count; i++)
        {
            yield return i * i;
        }
    }
}

// Przykład 3: Rekurencja
int Factorial(int n)
{
    if (n < 0) throw new ArgumentException();
    
    return Calculate(n);
    
    // Local recursive function
    static int Calculate(int num)
    {
        if (num <= 1) return 1;
        return num * Calculate(num - 1);
    }
}
💡 Local functions vs Lambda - kiedy czego używać?
void ProcessData(int[] numbers)
{
    // ✅ Local function - gdy:
    // - Używana wielokrotnie w metodzie
    // - Ma być nazwana (czytelność)
    // - Może być rekurencyjna
    bool IsEven(int n) => n % 2 == 0;
    
    foreach (var num in numbers)
    {
        if (IsEven(num))  // wielokrotne użycie
        {
            Console.WriteLine(num);
        }
    }
    
    // ✅ Lambda - gdy:
    // - Przekazywana do LINQ/metod wyższego rzędu
    // - Używana raz
    var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();
}
Lambda improvements - ewolucja (C# 9-14)

C# 9 - Static Lambdas

🎉 C# 9 - Static Lambdas

Lambda może być static - nie przechwytuje zmiennych z otoczenia (closure).

void ProcessNumbers(int[] numbers)
{
    int threshold = 10;
    
    // Normalna lambda - przechwytuje 'threshold' (closure)
    var filtered1 = numbers.Where(n => n > threshold).ToList();
    // Alokuje pamięć dla closure (przechowuje threshold)
    
    // C# 9 - Static lambda - NIE przechwytuje zmiennych
    var filtered2 = numbers.Where(static n => n > 0).ToList();
    // Brak alokacji! Szybsze!
    
    // ❌ To nie zadziała - static nie może użyć threshold
    // var filtered3 = numbers.Where(static n => n > threshold).ToList();
    // Błąd kompilacji! ✅ (zapobiega przypadkowemu closure)
}

// Kiedy używać static lambdas?
// 1. Performance-critical code (unikanie alokacji)
// 2. Gdy NIE potrzebujesz zmiennych z zewnątrz
// 3. Jako "dokumentacja" - "ta lambda jest niezależna"

C# 9 - Lambda attributes

// C# 9 - możesz dodać atrybuty do lambd

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

// Lambda z atrybutem (rzadko używane)
var result = numbers.Select([MyCustomAttribute] (int x) => x * 2);

// Częściej w event handlers
button.Click += [ObservableProperty] (sender, args) => { };

C# 10 - Natural type inference

// ❌ Przed C# 10 - musisz określić typ
Func<int, int> square1 = x => x * x;
Func<string, bool> isLong1 = s => s.Length > 10;

// ✅ C# 10 - var z lambdami (natural type inference)
var square2 = (int x) => x * x;  // kompilator wywnioskuje Func<int, int>
var isLong2 = (string s) => s.Length > 10;  // Func<string, bool>

// MUSISZ określić typy parametrów!
// var invalid = x => x * x;  // ❌ błąd - nie wiadomo jaki typ x

// Lambda z return type
var parse = int (string s) => int.Parse(s);  // jawny return type: int

// Lambda z attributes i return type
var processor = [MyAttribute] string (int x) => x.ToString();

C# 10 - Lambda return type inference

// Lambda może zwrócić różne typy - kompilator wywnioskuje wspólny typ bazowy

var getValue = (bool flag) => flag ? 1 : 2.5;
// Typ: Func>bool, double> (wspólny typ: int i double → double)

var getObject = (bool flag) => flag ? "text" : 123;
// Typ: Func>bool, object> (wspólny typ: string i int → object)

// Możesz wymusić konkretny return type
var enforceInt = int (bool flag) => flag ? 1 : 2;
// Typ: Func>bool, int>
🔥 C# 14 - Parameter modifiers w lambdach

Największa nowość - ref, out, in, params w lambdach!

🎉 REWOLUCJA C# 14 - Lambda parameter modifiers

Po raz pierwszy w historii C# możesz używać ref, out, in, params w lambdach!

❌ Przed C# 14 - niemożliwe!
// ❌ To NIE działało w C# 6-13!

// Nie mogłeś użyć ref w lambdzie
// var swap = (ref int a, ref int b) => { };
// BŁĄD!

// Nie mogłeś użyć out w lambdzie
// var tryParse = (string s, out int result) => { };
// BŁĄD!

// Musiałeś tworzyć normalną metodę 😞
✅ C# 14 - działa!
// ✅ C# 14 - wszystko działa!

// ref w lambdzie
var swap = (ref int a, ref int b) =>
{
    (a, b) = (b, a);
};

// out w lambdzie  
var tryParse = (string s, out int result) =>
{
    return int.TryParse(s, out result);
};

// Nareszcie! 🎉

ref parameters w lambdach

// ref - modyfikacja zmiennej przez referencję

var increment = (ref int value) => value++;

int x = 10;
increment(ref x);
Console.WriteLine(x);  // 11 (zmodyfikowane!)

// Swap - klasyczny przykład ref
var swap = (ref int a, ref int b) =>
{
    int temp = a;
    a = b;
    b = temp;
};

int first = 5, second = 10;
swap(ref first, ref second);
Console.WriteLine($"{first}, {second}");  // 10, 5

// ref z większymi strukturami - performance
readonly struct LargeStruct
{
    public int Value1 { get; init; }
    public int Value2 { get; init; }
    // ... wiele pól
}

// Unikamy kopiowania dużej struktury
var processLarge = (ref LargeStruct data) =>
{
    Console.WriteLine($"{data.Value1}, {data.Value2}");
};

out parameters w lambdach

// out - zwracanie wielu wartości

var divide = (int a, int b, out int remainder) =>
{
    remainder = a % b;
    return a / b;
};

int quotient = divide(17, 5, out int rem);
Console.WriteLine($"{quotient} remainder {rem}");  // 3 remainder 2

// TryParse pattern w lambdzie
var tryParseInt = (string input, out int result) =>
{
    if (int.TryParse(input, out result))
    {
        return true;
    }
    result = 0;
    return false;
};

if (tryParseInt("123", out int number))
{
    Console.WriteLine($"Parsed: {number}");
}

// GetMinMax w lambdzie
var getMinMax = (int[] numbers, out int min, out int max) =>
{
    min = numbers.Min();
    max = numbers.Max();
};

int[] nums = { 5, 2, 8, 1, 9 };
getMinMax(nums, out int minimum, out int maximum);
Console.WriteLine($"Min: {minimum}, Max: {maximum}");

in parameters w lambdach

// in - readonly reference (dla performance)

readonly struct Point3D
{
    public double X { get; init; }
    public double Y { get; init; }
    public double Z { get; init; }
}

// in - przekazanie przez referencję, ale read-only
var calculateDistance = (in Point3D p1, in Point3D p2) =>
{
    double dx = p2.X - p1.X;
    double dy = p2.Y - p1.Y;
    double dz = p2.Z - p1.Z;
    return Math.Sqrt(dx * dx + dy * dy + dz * dz);
    
    // p1.X = 10;  // ❌ BŁĄD! in jest read-only
};

var point1 = new Point3D { X = 0, Y = 0, Z = 0 };
var point2 = new Point3D { X = 3, Y = 4, Z = 0 };
double distance = calculateDistance(in point1, in point2);
Console.WriteLine(distance);  // 5

params w lambdach

// params - zmienna liczba argumentów

var sum = (params int[] numbers) =>
{
    int total = 0;
    foreach (int num in numbers)
    {
        total += num;
    }
    return total;
};

// Wywołania z różną liczbą argumentów
Console.WriteLine(sum(1));              // 1
Console.WriteLine(sum(1, 2));           // 3
Console.WriteLine(sum(1, 2, 3, 4, 5));  // 15

// Concat strings
var concat = (string separator, params string[] parts) =>
{
    return string.Join(separator, parts);
};

string path = concat("/", "home", "user", "documents");
Console.WriteLine(path);  // home/user/documents

// Max z dowolnej liczby wartości
var max = (params int[] values) =>
{
    if (values.Length == 0)
        throw new ArgumentException("Need at least one value");
    return values.Max();
};

Console.WriteLine(max(5, 2, 8, 1, 9));  // 9
💡 Praktyczne zastosowanie - lambda jako callback z ref/out
// Wcześniej musiałeś tworzyć named method
// Teraz możesz użyć lambdy inline!

class DataProcessor
{
    public delegate void ProcessDelegate(ref int value, out bool success);
    
    public void Process(int[] data, ProcessDelegate processor)
    {
        foreach (ref int item in data.AsSpan())
        {
            processor(ref item, out bool success);
            if (!success)
                Console.WriteLine("Processing failed");
        }
    }
}

var processor = new DataProcessor();
int[] data = { 1, 2, 3, 4, 5 };

// C# 14 - lambda inline z ref i out!
processor.Process(data, (ref int value, out bool success) =>
{
    if (value < 0)
    {
        success = false;
        return;
    }
    
    value *= 2;  // modyfikuj przez ref
    success = true;
});

Console.WriteLine(string.Join(", ", data));  // 2, 4, 6, 8, 10
⚠️ Kiedy używać parameter modifiers w lambdach?

ref/out/in/params w lambdach to zaawansowane ficzery! Używaj tylko gdy:

  • ✅ Performance-critical code (in dla dużych struct)
  • ✅ Callback API wymaga ref/out
  • ✅ Inline processing z modyfikacją (ref)
  • ❌ NIE nadużywaj - w większości przypadków normalne lambdy wystarczą

W 99% kodu nie potrzebujesz tego. Ale gdy potrzebujesz - masz tę możliwość w C# 14! 🎉

Async/Await - podstawy asynchroniczności

Synchroniczny vs Asynchroniczny kod

🔍 Czym jest kod asynchroniczny?

Synchroniczny - program czeka na zakończenie operacji (blokuje wątek)
Asynchroniczny - program kontynuuje działanie podczas oczekiwania

// Synchronicznie - czeka 5 sekund
Thread.Sleep(5000);  // ❌ Blokuje wątek!
DoSomethingElse();   // Wykona się dopiero po 5 sekundach

// Asynchronicznie - nie blokuje
await Task.Delay(5000);  // ✅ Nie blokuje wątku!
DoSomethingElse();       // Może się wykonać wcześniej (na innym wątku)

async/await - podstawowa składnia

// Synchroniczna metoda (blokująca)
string DownloadString(string url)
{
    using var client = new HttpClient();
    return client.GetStringAsync(url).Result;  // ❌ BLOKUJE wątek!
}

// Asynchroniczna metoda z async/await
async Task<string> DownloadStringAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);  // ✅ NIE blokuje!
}

// Użycie
string data = await DownloadStringAsync("https://api.example.com");
Console.WriteLine(data);
🎉 async/await - zasady
  • Metoda z await MUSI być oznaczona async
  • Async metoda zwraca Task (void) lub Task<T> (zwraca T)
  • await czeka na zakończenie Task bez blokowania wątku
  • Nazwa async metody kończy się na Async (konwencja)

Task<T> vs Task vs void

// Task<T> - zwraca wartość typu T
async Task GetNumberAsync()
{
    await Task.Delay(1000);
    return 42;
}

// Task - nie zwraca wartości (jak void)
async Task ProcessDataAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done");
}

// async void - TYLKO dla event handlers!
async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(1000);
    Console.WriteLine("Clicked");
}
// ⚠️ NIE używaj async void poza event handlerami!

// Wywołania
int number = await GetNumberAsync();  // czeka i zwraca 42
await ProcessDataAsync();  // czeka na zakończenie

Praktyczne przykłady async/await

// Przykład 1: HTTP request
async Task<string> FetchUserDataAsync(int userId)
{
    using var client = new HttpClient();
    string url = $"https://api.example.com/users/{userId}";
    string json = await client.GetStringAsync(url);
    return json;
}

// Przykład 2: Czytanie pliku
async Task<string> ReadFileAsync(string path)
{
    using var reader = new StreamReader(path);
    string content = await reader.ReadToEndAsync();
    return content;
}

// Przykład 3: Wiele async operacji równolegle
async Task<(string user, string posts, string comments)> FetchAllDataAsync(int userId)
{
    var userTask = FetchUserDataAsync(userId);
    var postsTask = FetchPostsAsync(userId);
    var commentsTask = FetchCommentsAsync(userId);
    
    // Czekaj na WSZYSTKIE równocześnie
    await Task.WhenAll(userTask, postsTask, commentsTask);
    
    return (
        await userTask,
        await postsTask,
        await commentsTask
    );
}

// Przykład 4: Retry pattern z async
async Task<T> RetryAsync<T>(Func<Task<T>> operation, int maxAttempts = 3)
{
    for (int i = 0; i < maxAttempts; i++)
    {
        try
        {
            return await operation();
        }
        catch (Exception ex) when (i < maxAttempts - 1)
        {
            Console.WriteLine($"Attempt {i + 1} failed: {ex.Message}");
            await Task.Delay(1000 * (i + 1));  // exponential backoff
        }
    }

    return await operation();  // ostatnia próba - rzuć wyjątek jeśli fail
}

// Użycie
var data = await RetryAsync(async () =>
{
    return await FetchUserDataAsync(123);
});

ConfigureAwait - zaawansowane

// ConfigureAwait(false) - nie wracaj do oryginalnego kontekstu

// W aplikacjach UI (WPF, WinForms, Blazor)
async Task UpdateUIAsync()
{
    var data = await FetchDataAsync();  // domyślnie wraca do UI thread
    UpdateTextBox(data);  // działa - jesteśmy na UI thread
}

// W bibliotekach (library code) - użyj ConfigureAwait(false)
async Task<string> LibraryMethodAsync()
{
    var data = await FetchDataAsync().ConfigureAwait(false);
    // NIE wraca do oryginalnego kontekstu - szybsze!
    return ProcessData(data);
}

// Zasada:
// - Aplikacje UI: NIE używaj ConfigureAwait(false)
// - Biblioteki: ZAWSZE używaj ConfigureAwait(false)
⚠️ Async/Await - częste błędy
// ❌ BŁĄD 1: async void (poza event handlers)
async void ProcessAsync()  // ❌ NIE!
{
    await Task.Delay(1000);
}
// Wyjątki są trudne do złapania, nie możesz await

// ✅ Poprawnie
async Task ProcessAsync()  // ✅ Task zamiast void
{
    await Task.Delay(1000);
}

// ❌ BŁĄD 2: .Result lub .Wait() - deadlock!
async Task GetNumberAsync()
{
    await Task.Delay(1000);
    return 42;
}

int number = GetNumberAsync().Result;  // ❌ DEADLOCK w UI apps!

// ✅ Poprawnie
int number = await GetNumberAsync();  // ✅ użyj await

// ❌ BŁĄD 3: async bez await
async Task DoSomethingAsync()  // ⚠️ warning: async bez await
{
    Console.WriteLine("Hello");
    // brak await - async jest niepotrzebne!
}

// ✅ Poprawnie - usuń async
Task DoSomethingAsync()  // lub zmień na void jeśli nie zwraca Task
{
    Console.WriteLine("Hello");
    return Task.CompletedTask;
}
Podsumowanie

Mega wpis o ewolucji metod i funkcji! Zobaczłeś jak C# zmienił się od 2015 do 2026:

  • Expression-bodied members (C# 6-7)=> dla metod, properties, constructors
  • 🔥 Local functions (C# 7+) – funkcje wewnątrz metod, static local functions
  • Static lambdas (C# 9) – unikanie closure, lepsza performance
  • Lambda improvements (C# 10) – var z lambdami, natural type inference
  • 🔥🔥 Parameter modifiers w lambdach (C# 14) – ref, out, in, params w lambdach!
  • Async/await podstawy – Task<T>, asynchroniczność, ConfigureAwait
  • Praktyczne wzorce – retry pattern, parallel operations, error handling

W kolejnym wpisie zagłębimy się w pojęcie null safety – bezpieczny dostęp, wartości domyślne, przypisz jeśli null i wiele, wiele więcej!

Zadanie dla Ciebie 🎯

Stwórz async system przetwarzania zamówień używając C# 14:

  1. Klasa OrderProcessor z async metodami:
    • Task<Order> FetchOrderAsync(int orderId)
    • Task ValidateOrderAsync(Order order)
    • Task ProcessPaymentAsync(Order order)
  2. Local function do walidacji wewnątrz ValidateOrderAsync
  3. Lambda z out parameter (C# 14) do sprawdzenia dostępności produktu
  4. Retry pattern dla ProcessPaymentAsync
  5. Przetwarzaj 5 zamówień równolegle używając Task.WhenAll
// Przykładowe użycie:
var processor = new OrderProcessor();

// Przetworz wiele zamówień równolegle
var orderIds = new[] { 1, 2, 3, 4, 5 };
var tasks = orderIds.Select(id => processor.ProcessOrderAsync(id));
var results = await Task.WhenAll(tasks);

Console.WriteLine($"Processed {results.Length} orders");
🎯 BONUS: Async Image Downloader z C# 14 features

Stwórz async image downloader używając WSZYSTKICH nowych ficzerów!

Wymagania:

  1. Async methods:
    • Task<byte[]> DownloadImageAsync(string url)
    • Task SaveImageAsync(byte[] data, string path)
    • Task<bool> ValidateImageAsync(byte[] data)
  2. Local functions:
    • Walidacja URL wewnątrz DownloadImageAsync
    • Retry logic jako local function
  3. C# 14 Lambda z ref/out:
    • Lambda z out bool success do walidacji
    • Lambda z ref int retryCount dla retry logic
  4. Expression-bodied members:
    • Properties dla statistics (ile pobranych, ile failed)
  5. Parallel download:
    • Pobierz 10 obrazków równolegle
    • Progress reporting
    • Error handling (continue on error)

Przykład użycia:

var downloader = new ImageDownloader();

var urls = new[]
{
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    // ... 10 URLs
};

var results = await downloader.DownloadAllAsync(urls, 
    progress: (current, total) => 
    {
        Console.WriteLine($"Progress: {current}/{total}");
    });

Console.WriteLine($"Downloaded: {downloader.SuccessCount}");
Console.WriteLine($"Failed: {downloader.FailedCount}");

Ten projekt łączy async/await, local functions, static lambdas, C# 14 parameter modifiers i expression-bodied members. Pokazujesz pełne opanowanie nowoczesnego C#! 🚀✨