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

String to najpopularniejszy typ w C# (oprócz int). Każda aplikacja używa tekstów - do wyświetlania UI, komunikacji z API, logowania, przechowywania danych. W tym wpisie nauczysz się wszystkiego o String'ach w C# 14 i .NET 10!

Poznasz raw string literals (C# 11), UTF-8 literals (C# 11), zaawansowaną string interpolation, Span<char> dla wydajności, i najlepsze praktyki pracy z tekstami w 2026 roku.

🔍 Czym jest string w C#?

string to typ referencyjny reprezentujący sekwencję znaków Unicode. Jest immutable (niezmienialny) - każda "zmiana" stringa tworzy nowy string w pamięci!

string s = "Hello";
s = s + " World";  // To NIE zmienia oryginalnego "Hello"!
                   // Tworzy NOWY string "Hello World"
                   // "Hello" zostaje w pamięci (garbage collector posprząta później)

string to alias dla System.String - to samo, krótsza nazwa.

String interpolation - podstawy i zaawansowane użycie

Podstawowa string interpolation (C# 6+)

string name = "Jan";
int age = 30;

// String interpolation z $
string message = $"Cześć, {name}! Masz {age} lat.";
Console.WriteLine(message);  // Cześć, Jan! Masz 30 lat.

// Możesz używać wyrażeń wewnątrz {}
string info = $"{name} urodzil się w {2026 - age} roku.";
Console.WriteLine(info);  // Jan urodzil się w 1996 roku.

// Wywołania metod
string upper = $"Imię wielkimi literami: {name.ToUpper()}";
Console.WriteLine(upper);  // Imię wielkimi literami: JAN
❌ Stary sposób - konkatenacja
string name = "Jan";
int age = 30;

// Brzydkie i podatne na błędy!
string message = "Cześć, " + name + "! Masz " + age + " lat.";

// Jeszcze gorsze z wieloma zmiennymi
string complex = "Użytkownik " + name + 
    " (lat: " + age + ") " +
    "zalogował się o " + DateTime.Now + ".";
// 😱 Nieczytelne!
✅ Nowoczesny sposób - interpolation
string name = "Jan";
int age = 30;

// Czytelne i przyjemne!
string message = $"Cześć, {name}! Masz {age} lat.";

// Łatwe do czytania nawet z wieloma zmiennymi
string complex = $"Użytkownik {name} (lat: {age}) 
    zalogował się o {DateTime.Now}.";
// ✅ Super czytelne!

Formatowanie w string interpolation

// Formatowanie liczb
decimal price = 1234.56m;
Console.WriteLine($"Cena: {price:C}");  // Cena: 1 234,56 zł (currency)
Console.WriteLine($"Cena: {price:N2}");  // Cena: 1 234,56 (number z 2 miejscami po przecinku)
Console.WriteLine($"Cena: {price:F1}");  // Cena: 1234,6 (fixed point, 1 miejsce)

// Formatowanie dat
DateTime now = DateTime.Now;
Console.WriteLine($"Data: {now:yyyy-MM-dd}");  // Data: 2026-02-06
Console.WriteLine($"Czas: {now:HH:mm:ss}");  // Czas: 14:30:45
Console.WriteLine($"Pełna: {now:yyyy-MM-dd HH:mm:ss}");  // Pełna: 2026-02-06 14:30:45

// Formatowanie procentów
double percentage = 0.1234;
Console.WriteLine($"Procent: {percentage:P}");  // Procent: 12,34%
Console.WriteLine($"Procent: {percentage:P0}");  // Procent: 12% (0 miejsc po przecinku)

// Padding (wyrównanie)
string product = "Laptop";
int quantity = 5;
Console.WriteLine($"{product,-20} {quantity,5}");  
// Laptop                    5
// -20 = wyrównanie do lewej, 20 znaków
// 5 = wyrównanie do prawej, 5 znaków
💡 Praktyczny przykład - faktura
var items = new[]
{
    (Name: "Laptop", Quantity: 2, Price: 2999.99m),
    (Name: "Mysz", Quantity: 5, Price: 49.99m),
    (Name: "Klawiatura", Quantity: 3, Price: 199.99m)
};

Console.WriteLine("FAKTURA");
Console.WriteLine("="​.PadRight(50, '='));
Console.WriteLine($"{"Produkt",-20} {"Ilość",6} {"Cena",10} {"Wartość",12}");
Console.WriteLine("-".PadRight(50, '-'));

decimal total = 0;
foreach (var item in items)
{
    decimal value = item.Quantity * item.Price;
    total += value;
    Console.WriteLine($"{item.Name,-20} {item.Quantity,6} {item.Price,10:C} {value,12:C}");
}

Console.WriteLine("="​.PadRight(50, '='));
Console.WriteLine($"{"RAZEM:",-38} {total,12:C}");

// Output:
// FAKTURA
// ==================================================
// Produkt                 Ilość       Cena      Wartość
// --------------------------------------------------
// Laptop                      2  2 999,99 zł   5 999,98 zł
// Mysz                        5     49,99 zł     249,95 zł
// Klawiatura                  3    199,99 zł     599,97 zł
// ==================================================
// RAZEM:                                       6 849,90 zł

Interpolated verbatim strings

// Verbatim string (@) - zachowuje znaki specjalne
string path1 = "C:\\Users\\Jan\\Documents";  // trzeba escape'ować \
string path2 = @"C:\Users\Jan\Documents";    // ✅ łatwiejsze!

// Połączenie $ i @ - interpolation + verbatim (C# 8+)
string username = "Jan";
string path = $@"C:\Users\{username}\Documents";
Console.WriteLine(path);  // C:\Users\Jan\Documents

// Kolejność ma znaczenie w C# < 11:
// C# 6-10: TYLKO $@, nie @$
string correct = $@"Path: {username}";  // ✅
// string wrong = @$"Path: {username}";  // ❌ błąd w C# < 11

// C# 11+: można obie kolejności
string both1 = $@"Path: {username}";  // ✅
string both2 = @$"Path: {username}";  // ✅ też działa od C# 11
🔥 Raw String Literals - C# 11

Problem z multi-line strings

// Stary sposób - verbatim string z @
string oldJson = @"{
    ""name"": ""Jan"",
    ""age"": 30,
    ""email"": ""jan@example.com""
}";
// Problem: trzeba escape'ować quotes (podwójne "")
// Brzydkie i podatne na błędy!

string oldHtml = @"<div class="" container"">
            <h1>Hello</h1>
            <p>World</p>
</div>";
// Znowu podwójne "" 😞

Rozwiązanie - raw string literals

🎉 NOWOŚĆ C# 11 - Raw String Literals

Użyj """ (trzy cudzysłowy) zamiast " - nie musisz escape'ować NICZEGO!

// Raw string literal - """ (trzy cudzysłowy)
string json = """
    {
        "name": "Jan",
        "age": 30,
        "email": "jan@example.com"
    }
    """;
// ✅ Nie trzeba escape'ować quotes!
// ✅ Zachowuje formatowanie!
// ✅ Super czytelne!

string html = """
            

Hello

World

"""; // ✅ Quotes działają normalnie! // Możesz użyć cudzysłowów wewnątrz bez problemu string withQuotes = """ She said: "Hello World!" He replied: "Hi there!" """;
🔍 Jak działają raw string literals?
  • Zaczynasz z """ (minimum 3 cudzysłowy)
  • Kończysz z """ (ta sama liczba cudzysłowów)
  • Zawartość jest między - BEZ escape'owania!
  • Zachowuje wszystkie znaki specjalne: ", \, \n, etc.
  • Automatycznie usuwa wspólne wcięcie (indentation)

Interpolation w raw strings

string name = "Jan";
int age = 30;

// Raw string + interpolation - użyj $"""
string message = $"""
    Witaj, {name}!
    Masz {age} lat.
    """;

// Jeśli potrzebujesz {} w tekście, użyj $$""" i {{}}
string json = $$"""
    {
        "name": "{{name}}",
        "age": {{age}},
        "metadata": {
            "created": "2026-02-06"
        }
    }
    """;
// $$ = interpolacja używa {{}}
// pojedyncze {} to literalne nawiasy!

Console.WriteLine(json);
// {
//     "name": "Jan",
//     "age": 30,
//     "metadata": {
//         "created": "2026-02-06"
//     }
// }
💡 Praktyczne przykłady raw strings
// SQL query bez escape'owania
string userId = "123";
string sql = $"""
    SELECT * FROM Users
    WHERE UserId = '{userId}'
    AND Status = 'Active'
    ORDER BY CreatedDate DESC
    """;

// Regex pattern
string emailPattern = """
    ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
    """;
// Nie trzeba escape'ować \ w regex! ✅

// HTML template
string title = "Moja Strona";
string content = "Witaj na stronie!";
string htmlTemplate = $$"""
                <!DOCTYPE html>
                <html>
                <head>
                <title>{{title}}</title>
    </head>
                <body>
                <h1>{{title}}</h1>
                <p>{{content}}</p>
                <script>
            console.log("Hello from {{title}}!");
        </script>
    </body>
    </html>
    """;

// JSON z zagnieżdżonymi obiektami
string jsonConfig = $$"""
    {
        "app": {
            "name": "MyApp",
            "version": "1.0.0",
            "settings": {
                "debug": {{true.ToString().ToLower()}},
                "maxUsers": {{100}}
            }
        }
    }
    """;

Indentation w raw strings

void PrintMessage()
{
    string message = """
        Linia 1
        Linia 2
            Linia 3 (wcięta)
        Linia 4
        """;
    
    Console.WriteLine(message);
}

// Output (wcięcie automatycznie usunięte względem zamykającego """):
// Linia 1
// Linia 2
//     Linia 3 (wcięta)
// Linia 4

// Zamykające """ określa poziom wcięcia!
void Example2()
{
        string text = """
            To jest
            wieloliniowy
            tekst
            """;  // wcięcie 8 spacji - usunięte!
}
🔥 UTF-8 String Literals - C# 11

Problem z kodowaniem

Normalnie, stringi w C# są kodowane jako UTF-16 (2 bajty na znak). Gdy wysyłasz dane po sieci (HTTP, gRPC), zazwyczaj używasz UTF-8. To wymaga konwersji:

// Stary sposób - konwersja UTF-16 → UTF-8
string message = "Hello World";
byte[] utf8Bytes = Encoding.UTF8.GetBytes(message);  // alokacja + konwersja!

// Używane w HTTP, gRPC, JSON serialization, etc.
await httpClient.PostAsync(url, new ByteArrayContent(utf8Bytes));
⚡ Problem wydajnościowy

Każda konwersja UTF-16 → UTF-8:

  • Alokuje nową tablicę bajtów (heap allocation)
  • Kopiuje i konwertuje każdy znak
  • Garbage Collector musi potem posprzątać

W aplikacjach webowych może to się dziać MILIONY razy na sekundę! 😱

Rozwiązanie - UTF-8 literals

🎉 NOWOŚĆ C# 11 - UTF-8 String Literals

Dodaj u8 na końcu stringa - kompilator wygeneruje UTF-8 bezpośrednio!

// UTF-8 literal - dodaj u8 na końcu
ReadOnlySpan utf8Message = "Hello World"u8;
// ✅ ZERO alokacji!
// ✅ ZERO konwersji!
// ✅ UTF-8 bytes bezpośrednio w binary!

// Porównanie:
// Stary sposób:
string str = "Hello";  // UTF-16 string w pamięci
byte[] bytes = Encoding.UTF8.GetBytes(str);  // konwersja + alokacja

// Nowy sposób:
ReadOnlySpan utf8 = "Hello"u8;  // UTF-8 bytes bezpośrednio!
💡 Praktyczne użycie UTF-8 literals
// HTTP headers (często używane w web APIs)
ReadOnlySpan contentType = "application/json"u8;
ReadOnlySpan authHeader = "Bearer token123"u8;

// JSON payloads
ReadOnlySpan jsonPayload = """{"status":"ok"}"""u8;

// Protokoły sieciowe
ReadOnlySpan httpGet = "GET / HTTP/1.1\r\n"u8;

// Porównanie wydajności:
// ❌ Wolne (alokacja + konwersja)
byte[] slow = Encoding.UTF8.GetBytes("application/json");

// ✅ Szybkie (zero-allocation!)
ReadOnlySpan fast = "application/json"u8;

Kiedy używać UTF-8 literals?

  • ✅ HTTP headers i body
  • ✅ JSON serialization/deserialization
  • ✅ gRPC communication
  • ✅ Binary protocols
  • ✅ File I/O z UTF-8
  • ✅ Websockets
  • ❌ NIE używaj dla UI (string jest OK)
  • ❌ NIE używaj gdy i tak potrzebujesz string później
Operacje na stringach

Podstawowe metody

string text = "  Hello World  ";

// Length - długość
Console.WriteLine(text.Length);  // 15 (ze spacjami)

// Trim - usuwa białe znaki z początku i końca
Console.WriteLine(text.Trim());  // "Hello World"
Console.WriteLine(text.TrimStart());  // "Hello World  "
Console.WriteLine(text.TrimEnd());  // "  Hello World"

// ToUpper / ToLower
Console.WriteLine(text.ToUpper());  // "  HELLO WORLD  "
Console.WriteLine(text.ToLower());  // "  hello world  "

// Substring - wycinek stringa
string hello = "Hello World";
Console.WriteLine(hello.Substring(0, 5));  // "Hello"
Console.WriteLine(hello.Substring(6));  // "World"

// C# 8+ Range operator - czytelniejszy!
Console.WriteLine(hello[0..5]);  // "Hello"
Console.WriteLine(hello[6..]);  // "World"
Console.WriteLine(hello[^5..]);  // "World" (5 znaków od końca)

// Replace
Console.WriteLine(hello.Replace("World", "C#"));  // "Hello C#"

// Contains / StartsWith / EndsWith
Console.WriteLine(hello.Contains("World"));  // true
Console.WriteLine(hello.StartsWith("Hello"));  // true
Console.WriteLine(hello.EndsWith("World"));  // true

// IndexOf - znajdź pozycję
Console.WriteLine(hello.IndexOf("World"));  // 6
Console.WriteLine(hello.IndexOf("xyz"));  // -1 (nie znaleziono)

Split i Join

// Split - rozdziel string na tablicę
string csv = "Jan,Kowalski,30,Warszawa";
string[] parts = csv.Split(',');
// ["Jan", "Kowalski", "30", "Warszawa"]

Console.WriteLine(parts[0]);  // Jan
Console.WriteLine(parts[1]);  // Kowalski

// Split z wieloma separatorami
string text = "a,b;c:d";
string[] tokens = text.Split(',', ';', ':');
// ["a", "b", "c", "d"]

// Join - połącz tablicę w string
string[] words = { "Hello", "beautiful", "World" };
string sentence = string.Join(" ", words);
Console.WriteLine(sentence);  // "Hello beautiful World"

string csvLine = string.Join(",", new[] { "Jan", "Kowalski", "30" });
Console.WriteLine(csvLine);  // "Jan,Kowalski,30"

String comparison - porównywanie

string a = "Hello";
string b = "hello";

// == porównuje WARTOŚCI (nie referencje!)
Console.WriteLine(a == b);  // false (różna wielkość liter)

//Equals - to samo co ==
Console.WriteLine(a.Equals(b));  // false

// Porównanie case-insensitive
Console.WriteLine(a.Equals(b, StringComparison.OrdinalIgnoreCase));  // true!

// CompareTo - porządek alfabetyczny
Console.WriteLine("apple".CompareTo("banana"));  // -1 (apple < banana)
Console.WriteLine("banana".CompareTo("apple"));  // 1 (banana > apple)
Console.WriteLine("apple".CompareTo("apple"));  // 0 (równe)
⚠️ String comparison - pułapki
// ❌ ŹLE - porównanie case-sensitive gdy nie powinno być
string userInput = "yes";
if (userInput == "Yes")  // false! (różna wielkość)
{
    // Nie wykona się!
}

// ✅ DOBRZE - case-insensitive
if (userInput.Equals("Yes", StringComparison.OrdinalIgnoreCase))
{
    // Wykona się! ✅
}

// Jeszcze lepiej z pattern matching (C# 11+)
if (userInput is "yes" or "Yes" or "YES")
{
    // Wykona się dla wszystkich wariantów
}

IsNullOrEmpty i IsNullOrWhiteSpace

string? empty = "";
string? whitespace = "   ";
string? nullString = null;
string? valid = "Hello";

// IsNullOrEmpty - sprawdza czy null LUB pusty string
Console.WriteLine(string.IsNullOrEmpty(empty));  // true
Console.WriteLine(string.IsNullOrEmpty(whitespace));  // false (są spacje!)
Console.WriteLine(string.IsNullOrEmpty(nullString));  // true
Console.WriteLine(string.IsNullOrEmpty(valid));  // false

// IsNullOrWhiteSpace - sprawdza czy null LUB pusty LUB same białe znaki
Console.WriteLine(string.IsNullOrWhiteSpace(empty));  // true
Console.WriteLine(string.IsNullOrWhiteSpace(whitespace));  // true ✅
Console.WriteLine(string.IsNullOrWhiteSpace(nullString));  // true
Console.WriteLine(string.IsNullOrWhiteSpace(valid));  // false
💡 Kiedy używać której metody?
// Walidacja inputu użytkownika - użyj IsNullOrWhiteSpace
string? userName = GetUserInput();
if (string.IsNullOrWhiteSpace(userName))
{
    Console.WriteLine("Imię nie może być puste!");
    return;
}

// Sprawdzanie czy JSON field istnieje - użyj IsNullOrEmpty
string? jsonField = GetJsonField("name");
if (!string.IsNullOrEmpty(jsonField))
{
    // Pole istnieje i ma wartość
}
StringBuilder - dla wydajności

Problem z konkatenacją stringów

// ❌ BARDZO WOLNE gdy wiele konkatenacji
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString() + ",";  // Każda iteracja tworzy NOWY string!
}
// 1000 iteracji = 1000 alokacji stringów!
// Garbage Collector płacze 😭
🔍 Dlaczego to jest wolne?

String jest immutable. Każde result += ...:

  1. Alokuje NOWY string w pamięci
  2. Kopiuje cały result do nowego stringa
  3. Dodaje nową część
  4. Stary result zostaje jako garbage

1000 konkatenacji = 1000 alokacji = 💥 performance killer!

Rozwiązanie - StringBuilder

// ✅ SZYBKIE - StringBuilder
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
    sb.Append(',');
}
string result = sb.ToString();  // Tylko JEDNA alokacja na końcu!

// StringBuilder jest mutable - modyfikujesz ten sam obiekt
// Append dodaje do bufora (nie tworzy nowych stringów)

StringBuilder API

var sb = new StringBuilder();

// Append - dodaj na koniec
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");

// AppendLine - dodaj + nowa linia
sb.AppendLine("Linia 1");
sb.AppendLine("Linia 2");

// AppendFormat - formatowanie (stary styl)
sb.AppendFormat("Liczba: {0}, Tekst: {1}", 42, "test");

// C# 10+ AppendInterpolatedString - interpolation!
int age = 30;
string name = "Jan";
sb.AppendLine($"Użytkownik {name} ma {age} lat");

// Insert - wstaw w określonym miejscu
sb.Insert(0, "START: ");  // wstaw na początku

// Replace - zamień wszystkie wystąpienia
sb.Replace("World", "C#");

// Remove - usuń zakres znaków
sb.Remove(0, 5);  // usuń pierwsze 5 znaków

// Clear - wyczyść całość
sb.Clear();

// ToString - konwertuj do string
string finalString = sb.ToString();

// Length i Capacity
Console.WriteLine($"Length: {sb.Length}");  // aktualna długość
Console.WriteLine($"Capacity: {sb.Capacity}");  // zarezerwowana pojemność
💡 Praktyczny przykład - generowanie CSV
var users = new[]
{
    new { Id = 1, Name = "Jan", Email = "jan@example.com", Age = 30 },
    new { Id = 2, Name = "Anna", Email = "anna@example.com", Age = 25 },
    new { Id = 3, Name = "Piotr", Email = "piotr@example.com", Age = 35 }
};

var csv = new StringBuilder();
// Header
csv.AppendLine("Id,Name,Email,Age");

// Data rows
foreach (var user in users)
{
    csv.Append(user.Id);
    csv.Append(',');
    csv.Append(user.Name);
    csv.Append(',');
    csv.Append(user.Email);
    csv.Append(',');
    csv.AppendLine(user.Age.ToString());
}

string csvContent = csv.ToString();
Console.WriteLine(csvContent);

// Output:
// Id,Name,Email,Age
// 1,Jan,jan@example.com,30
// 2,Anna,anna@example.com,25
// 3,Piotr,piotr@example.com,35

Kiedy używać StringBuilder?

❌ StringBuilder NIE jest potrzebny
// Kilka konkatenacji - string interpolation OK
string name = "Jan";
int age = 30;
string message = $"Hello {name}, masz {age} lat";
// ✅ To jest OK! Nie trzeba StringBuilder

// Join - string.Join jest szybki
var words = new[] { "a", "b", "c" };
string joined = string.Join(",", words);
// ✅ To jest OK!
✅ StringBuilder potrzebny
// Pętla z wieloma konkatenacjami
var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
    sb.Append(',');
}
// ✅ StringBuilder! String by był wolny

// Budowanie dużego dokumentu
var html = new StringBuilder();
foreach (var item in items)
{
    html.AppendLine("
  • "); html.Append(item.Name); html.AppendLine("
  • "); } // ✅ StringBuilder dla wielu append
    ⚡ Zasada: StringBuilder gdy 5+ konkatenacji w pętli

    Złota zasada: Jeśli masz pętlę która buduje string, użyj StringBuilder. Dla 2-3 konkatenacji, zwykły string interpolation jest OK (kompilator może to zoptymalizować).

    Span<char> - zero-allocation string operations

    Problem z Substring

    // Substring alokuje NOWY string
    string original = "Hello World";
    string sub = original.Substring(0, 5);  // "Hello"
    // Problem: alokacja nowego stringa w pamięci!
    
    // W pętli to jest drogie:
    for (int i = 0; i < 1000; i++)
    {
        string part = original.Substring(0, 5);  // 1000 alokacji!
        // ... użycie part
    }
    

    Rozwiązanie - Span<char>

    🎉 Span<T> - zero-allocation slicing

    Span<char> to "view" (widok) na fragment stringa - NIE kopiuje danych!

    string original = "Hello World";
    
    // AsSpan() tworzy Span - zero-allocation!
    ReadOnlySpan span = original.AsSpan();
    
    // Slice - jak Substring, ale BEZ alokacji!
    ReadOnlySpan hello = span.Slice(0, 5);  // "Hello" (bez alokacji!)
    ReadOnlySpan world = span.Slice(6, 5);  // "World" (bez alokacji!)
    
    // Range operator - jeszcze czytelniej
    ReadOnlySpan hello2 = span[0..5];  // "Hello"
    ReadOnlySpan world2 = span[6..11];  // "World"
    
    // W pętli - ZERO alokacji!
    for (int i = 0; i < 1000; i++)
    {
        ReadOnlySpan part = original.AsSpan(0, 5);  // zero alokacji! 🎉
        // ... użycie part
    }
    
    🔍 Jak działa Span<T>?

    Span<T> to "okno" na istniejące dane. Nie kopiuje - tylko wskazuje na fragment:

    string text = "Hello World";
    //             01234567890
    
    ReadOnlySpan hello = text.AsSpan(0, 5);
    // hello wskazuje na znaki 0-4 w oryginalnym stringu
    // NIE tworzy nowego stringa!

    To jak wskaźnik w C, ale bezpieczny (C# pilnuje granic)!

    Implicit Span conversion - C# 14

    🎉 NOWOŚĆ C# 14 - Implicit Span Conversions

    Automatyczna konwersja string → ReadOnlySpan<char>!

    // Przed C# 14 - musisz wywołać AsSpan()
    ReadOnlySpan span1 = "Hello".AsSpan();
    
    // C# 14 - automatyczna konwersja!
    ReadOnlySpan span2 = "Hello";  // ✅ działa automatycznie!
    
    // Szczególnie przydatne w metodach
    void ProcessText(ReadOnlySpan text)
    {
        // ...
    }
    
    // C# 14 - możesz przekazać string bezpośrednio!
    ProcessText("Hello World");  // ✅ automatyczna konwersja do Span!
    
    // Przed C# 14 - musiałeś:
    ProcessText("Hello World".AsSpan());
    

    Span operations

    ReadOnlySpan text = "Hello World";
    
    // Length
    Console.WriteLine(text.Length);  // 11
    
    // Indexer
    Console.WriteLine(text[0]);  // 'H'
    Console.WriteLine(text[6]);  // 'W'
    
    // Slice
    ReadOnlySpan<char> hello = text[0..5];
    ReadOnlySpan<char> world = text[6..];
    
    // StartsWith / EndsWith (bez alokacji!)
    bool startsHello = text.StartsWith("Hello");  // true
    bool endsWorld = text.EndsWith("World");  // true
    
    // IndexOf (bez alokacji!)
    int index = text.IndexOf('W');  // 6
    
    // Contains (C# 8+)
    bool contains = text.Contains("Wor", StringComparison.Ordinal);  // true
    
    // Trim (zwraca Span, nie string!)
    ReadOnlySpan<char> trimmed = "  Hello  ".AsSpan().Trim();  // "Hello"
    
    // ToString() - gdy potrzebujesz string
    string converted = hello.ToString();  // "Hello" (alokacja!)
    
    💡 Praktyczny przykład - parsing CSV
    // CSV line parsing BEZ alokacji stringów!
    string csvLine = "Jan,Kowalski,30,Warszawa";
    ReadOnlySpan<char> span = csvLine;
    
    int firstComma = span.IndexOf(',');
    ReadOnlySpan<char> firstName = span[..firstComma];  // "Jan"
    span = span[(firstComma + 1)..];  // "Kowalski,30,Warszawa"
    
    int secondComma = span.IndexOf(',');
    ReadOnlySpan<char> lastName = span[..secondComma];  // "Kowalski"
    span = span[(secondComma + 1)..];  // "30,Warszawa"
    
    int thirdComma = span.IndexOf(',');
    ReadOnlySpan<char> ageSpan = span[..thirdComma];  // "30"
    ReadOnlySpan<char> city = span[(thirdComma + 1)..];  // "Warszawa"
    
    // Parse age z Span (zero alokacji!)
    int age = int.Parse(ageSpan);
    
    Console.WriteLine($"Imię: {firstName.ToString()}");
    Console.WriteLine($"Nazwisko: {lastName.ToString()}");
    Console.WriteLine($"Wiek: {age}");
    Console.WriteLine($"Miasto: {city.ToString()}");
    
    // Cały parsing BEZ alokacji (oprócz końcowego ToString)!

    Kiedy używać Span<char>?

    • ✅ Parsing (CSV, JSON, XML)
    • ✅ String processing w pętlach
    • ✅ Performance-critical code
    • ✅ Mikro-optymalizacje w hot paths
    • ❌ NIE używaj dla normalnego kodu aplikacyjnego
    • ❌ Span nie może być polem klasy (tylko zmienna lokalna/parametr)
    ⚠️ Ograniczenia Span<T>

    Span<T> to ref struct - może być TYLKO na stacku:

    class MyClass
    {
        // ❌ BŁĄD - Span nie może być polem klasy!
        private Span<char> _data;
        
        // ❌ BŁĄD - Span nie może być w async metodzie!
        async Task ProcessAsync(Span<char> data) { }
        
        // ✅ OK - jako parametr w synchronicznej metodzie
        void Process(Span<char> data) { }
        
        // ✅ OK - jako zmienna lokalna
        void Method()
        {
            Span<char> local = stackalloc char[100];
        }
    }

    Jeśli potrzebujesz przechować długoterminowo, użyj Memory<T>.

    Podsumowanie

    To był mega wpis o String'ach! Nauczyłeś się wszystkiego o pracy z tekstami w C# 14:

    • String interpolation – $"...", formatowanie liczb/dat, padding
    • Verbatim strings – @"..." i $@"..." dla ścieżek
    • 🔥 Raw string literals (C# 11) – """...""" bez escape'owania!
    • Raw interpolation – $"""...""" i $$"""..."""
    • 🔥 UTF-8 literals (C# 11) – "..."u8 dla zero-allocation
    • String operations – Trim, Split, Join, Replace, Contains
    • String comparison – OrdinalIgnoreCase, pułapki
    • IsNullOrEmpty vs IsNullOrWhiteSpace
    • StringBuilder – dla wydajnej konkatenacji w pętlach
    • Span<char> – zero-allocation string processing
    • 🔥 Implicit Span conversion (C# 14) – string → ReadOnlySpan automatycznie

    W kolejnym wpisie nauczymy się o kolekcjach: od tablic do Collection Expressions!

    Zadanie dla Ciebie 🎯

    Stwórz program "Log Parser":

    1. Przeczytaj plik log.txt z wieloma liniami logów
    2. Każda linia ma format: [2026-02-06 14:30:45] [INFO] User logged in: jan@example.com
    3. Wyciągnij z każdej linii:
      • Datę i czas
      • Level (INFO/ERROR/WARNING)
      • Wiadomość
    4. Zlicz ile jest logów każdego typu (INFO, ERROR, WARNING)
    5. Wypisz wszystkie linie z ERROR
    6. Użyj ReadOnlySpan<char> do parsingu!
    // Przykładowa linia:
    string logLine = "[2026-02-06 14:30:45] [INFO] User logged in: jan@example.com";
    
    // Twój kod powinien wyciągnąć:
    // DateTime: 2026-02-06 14:30:45
    // Level: INFO
    // Message: User logged in: jan@example.com
    
    🎯 BONUS: SQL Query Builder

    Stwórz klasę SqlQueryBuilder która używa nowoczesnych features C# 14:

    Wymagania:

    1. StringBuilder do budowania query
    2. Raw string literals dla SQL templates
    3. Metody:
      • Select(params string[] columns)
      • From(string table)
      • Where(string condition)
      • OrderBy(string column)
      • Build() - zwraca gotowe query jako string
    4. Fluent API (method chaining)

    Przykład użycia:

    var query = new SqlQueryBuilder()
        .Select("Id", "Name", "Email", "CreatedDate")
        .From("Users")
        .Where("Age > 18")
        .Where("IsActive = 1")
        .OrderBy("CreatedDate DESC")
        .Build();
    
    Console.WriteLine(query);
    
    // Output:
    // SELECT Id, Name, Email, CreatedDate
    // FROM Users
    // WHERE Age > 18
    //   AND IsActive = 1
    // ORDER BY CreatedDate DESC

    Bonus challenges:

    • Parametryzowane queries (zapobieganie SQL injection)
    • Join support
    • Group By + Having
    • Limit/Offset (paginacja)

    Ten projekt łączy StringBuilder, raw strings, fluent API i praktyczne zastosowanie. Produkcyjny kod! 🚀✨