• C# i .NET
  • String w C# - null, niezmienność i StringBuilder

String w C# - null, niezmienność i StringBuilder

Radosław Krajewski 22 maja 2026
Diagram pokazuje, jak StringBuilder tworzy nowe bufory znaków. To jest string co to.

Spis treści

Gdy w kodzie pojawia się string, zwykle pracujesz z tekstem: nazwą użytkownika, adresem e-mail, ścieżką pliku albo odpowiedzią z API. Wyjaśnię, czym dokładnie jest ten typ w C# i .NET, jak tworzyć wartości tekstowe, czym różnią się null i pusty tekst oraz kiedy zwykły string warto zastąpić StringBuilder.

String przechowuje tekst, ale sposób jego użycia ma znaczenie

  • string to typ reprezentujący ciąg znaków, będący aliasem dla System.String.
  • Obiekty typu string są niezmienne, więc każda zmiana tekstu tworzy nową wartość.
  • Pusty tekst, null i tekst zawierający same spacje oznaczają trzy różne sytuacje.
  • Do czytelnego składania tekstu najlepiej używać interpolacji, a do wielokrotnych modyfikacji StringBuilder.
  • Porównując tekst, trzeba świadomie wybrać reguły porównania, zwłaszcza przy danych technicznych i wielojęzycznych.

Diagram ilustrujący działanie StringBuilder. Zobacz, string co to znaczy, gdy tekst jest dodawany do bufora.

Co oznacza string w C# i .NET

string to typ danych służący do przechowywania ciągu znaków. Może reprezentować pojedyncze słowo, długi opis, kod JSON, adres URL, zawartość pliku albo komunikat wyświetlany użytkownikowi.

string imie = "Anna";
string komunikat = "Witaj w aplikacji";
string pustyTekst = "";

W języku C# zapis string jest skrótem, czyli aliasem dla klasy System.String. Obie formy opisują ten sam typ, dlatego poniższe deklaracje są równoważne.

string nazwa = "Kursdotnet.pl";
System.String drugaNazwa = "Kursdotnet.pl";

Technicznie string jest typem referencyjnym, ale w codziennej pracy zachowuje się bardzo wygodnie. Możesz porównywać tekst operatorem ==, ponieważ C# przeciąża ten operator dla zawartości napisów, a nie tylko dla adresów obiektów.

string a = "C#";
string b = "C#";

bool wynik = a == b; // true

Nie należy mylić string z char. char przechowuje pojedynczy znak i używa apostrofów, natomiast string przechowuje tekst zapisany w cudzysłowie.

char litera = 'A';
string slowo = "Azure";

Jak tworzyć tekst i umieszczać w nim wartości

Najprostszy napis zapisuje się w zwykłych cudzysłowach. Znaki specjalne, takie jak nowa linia czy cudzysłów wewnątrz tekstu, zapisuje się za pomocą sekwencji ucieczki.

string tekst = "Pierwsza linia\nDruga linia";
string cytat = "Powiedział: \"Cześć\"";

W praktyce najczęściej składam tekst za pomocą interpolacji. Znak $ przed napisem pozwala wstawiać wartości wyrażeń bez ręcznego łączenia fragmentów operatorem +.

string imie = "Marek";
int punkty = 125;

string opis = $"{imie} ma {punkty} punktów.";

Interpolacja obsługuje także formatowanie liczb i dat. To czytelniejsza forma, szczególnie gdy komunikat zawiera kilka zmiennych.

decimal cena = 149.99m;
DateTime data = DateTime.Now;

string komunikat = $"Cena: {cena:C}, data: {data:dd.MM.yyyy}";

Ścieżki plików i tekst wieloliniowy

Przy ścieżkach systemu Windows można użyć napisu werbatimowego z prefiksem @. Backslash nie jest wtedy traktowany jako początek sekwencji specjalnej.

string sciezka = @"C:\Projekty\Aplikacja\plik.json";

Od C# 11 dostępne są także napisy surowe, otaczane potrójnymi cudzysłowami. Dobrze sprawdzają się przy JSON-ie, XML-u, SQL-u, wyrażeniach regularnych i dłuższych blokach tekstu.

string json = """
{
  "name": "Anna",
  "role": "Developer"
}
""";

Jeśli tekst zawiera zarówno zmienne, jak i wieloliniową strukturę, można połączyć napis surowy z interpolacją.

string imie = "Anna";

string json = $$"""
{
  "name": "{{imie}}"
}
""";

Liczba znaków dolara wpływa na to, ile nawiasów klamrowych rozpoczyna interpolację. Ten szczegół jest przydatny przy generowaniu JSON-u lub szablonów, w których klamry są częścią wyniku.

Nieimmutowalność, null i długość tekstu

Najważniejszą właściwością tego typu jest niezmienność. Gdy zmieniasz wartość tekstową, nie modyfikujesz istniejącego obiektu, tylko tworzysz nowy napis.

string tekst = "Ala";
tekst += " ma kota";

Po wykonaniu drugiej instrukcji powstaje nowa wartość. Przy kilku prostych operacjach nie ma to znaczenia, ale w pętli wykonującej tysiące konkatenacji może prowadzić do niepotrzebnych alokacji pamięci. Właśnie dlatego dokumentacja Microsoft Learn rozdziela zwykłą pracę z tekstem od scenariuszy, w których lepszym narzędziem jest StringBuilder.

Pusty napis to nie to samo co null

Pusty napis "" istnieje i ma długość równą zero. Wartość null oznacza natomiast brak obiektu, więc próba wywołania na niej właściwości lub metody może zakończyć się wyjątkiem.

string pusty = "";
string? brakWartosci = null;

Console.WriteLine(pusty.Length);        // 0
Console.WriteLine(brakWartosci?.Length); // puste miejsce

Przy włączonych nullable reference types zapis string? jasno informuje kompilator i innych programistów, że zmienna może mieć wartość null. To drobna adnotacja, która pomaga wcześniej wykrywać wiele błędów.

Długość nie zawsze oznacza liczbę znaków widocznych dla człowieka

Właściwość Length zwraca liczbę jednostek UTF-16, czyli elementów typu char. Dla większości polskich liter wynik odpowiada temu, czego oczekujemy, ale niektóre emoji i znaki spoza podstawowej płaszczyzny Unicode zajmują dwie jednostki.

string emoji = "😀";

Console.WriteLine(emoji.Length); // 2

Jeżeli tworzysz limit długości pola dla użytkownika, samo Length może więc nie wystarczyć. Przy zaawansowanej obsłudze tekstu trzeba uwzględnić punkty kodowe albo grafemy, czyli znaki postrzegane przez użytkownika jako pojedyncze elementy.

Jak poprawnie porównywać i przeszukiwać stringi

Porównywanie tekstu nie zawsze oznacza zwykłe sprawdzenie, czy dwa ciągi mają identyczne znaki. Czasem wielkość liter ma znaczenie, a czasem nie. Inaczej traktuje się też dane techniczne, takie jak identyfikatory, i tekst przeznaczony do wyświetlenia człowiekowi.

string kod = "admin";

bool identyczne = string.Equals(
    kod,
    "ADMIN",
    StringComparison.OrdinalIgnoreCase
);

Dla kluczy, nazw technicznych, tokenów i identyfikatorów zwykle wybieram StringComparison.Ordinal albo OrdinalIgnoreCase. Takie porównanie nie zależy od języka ani ustawień regionalnych komputera, więc zachowuje się przewidywalnie.

Przy sortowaniu i prezentowaniu tekstu użytkownikowi sensowniejsze może być porównanie kulturowe, na przykład CurrentCulture. To rozróżnienie jest ważne, bo reguły alfabetu tureckiego, polskiego czy niemieckiego nie zawsze pokrywają się z prostym porównaniem kodów znaków.

bool znaleziono = nazwa.Contains(
    "azure",
    StringComparison.OrdinalIgnoreCase
);

W nowych fragmentach kodu warto podawać sposób porównania jawnie. Unikam wtedy niejasności i łatwiej ocenić, czy kod działa poprawnie po wdrożeniu na komputerze z inną kulturą systemową.

Przeczytaj również: Łączenie danych w C# z LINQ - Join, GroupJoin i lewy join

Operacje, które przydają się najczęściej

string tekst = "  C# i .NET 2026  ";

string bezSpacji = tekst.Trim();
string duzeLitery = tekst.ToUpperInvariant();
string fragment = tekst.Substring(2, 2);
string[] slowa = tekst.Split(' ', StringSplitOptions.RemoveEmptyEntries);
string zamieniony = tekst.Replace("2026", "kurs");

Metody takie jak Trim, Split i Replace zwracają nowe wartości. Nie zmieniają oryginalnego napisu, co często zaskakuje osoby, które po raz pierwszy pracują z niezmiennymi typami.

Kiedy zwykły string wystarczy, a kiedy wybrać StringBuilder

Do pojedynczych komunikatów, wartości formularzy, nazw i odpowiedzi z API zwykły string jest właściwym wyborem. Jego kod jest prosty, czytelny i zwykle nie wymaga żadnych dodatkowych optymalizacji.

Inaczej wygląda generowanie dużego tekstu w pętli. Każda operacja += może utworzyć nowy obiekt, skopiować dotychczasową zawartość i dołączyć kolejny fragment. Przy małej liczbie operacji nie jest to problem, ale przy dużych danych niepotrzebnie obciąża pamięć i procesor.

var builder = new StringBuilder();

for (int i = 1; i <= 1000; i++)
{
    builder.Append("Wiersz ");
    builder.AppendLine(i.ToString());
}

string wynik = builder.ToString();
Potrzeba Lepszy wybór Dlaczego
Pojedynczy komunikat string z interpolacją Najlepsza czytelność i prosty kod
Kilka połączeń tekstu string Różnica wydajności zwykle będzie pomijalna
Wiele modyfikacji w pętli StringBuilder Mniej pośrednich obiektów i kopiowania danych
Duży tekst tylko do odczytu string Po zakończeniu budowania wygodnie przekazać gotową wartość

Nie zamieniam automatycznie każdego + na StringBuilder. Przedwczesna optymalizacja pogarsza kod bez realnej korzyści. Najpierw oceniam liczbę operacji i rozmiar danych, a dopiero potem wybieram narzędzie.

Typowe błędy podczas pracy z tekstem

Jednym z częstszych problemów jest traktowanie wartości pochodzącej od użytkownika jak zaufanego tekstu. Sam typ string nie zabezpiecza przed SQL injection, nieprawidłowym HTML-em ani niebezpieczną zawartością. Bezpieczeństwo zależy od tego, gdzie tekst zostanie użyty i jak zostanie zakodowany lub sparametryzowany.

Drugim błędem jest porównywanie danych technicznych bez określenia reguł. Dla loginu, kodu statusu czy klucza konfiguracji zwykle potrzebujesz porównania niezależnego od kultury. Dla tekstu prezentowanego człowiekowi sytuacja może być inna.

Trzeci problem to mylenie null, pustego tekstu i białych znaków. Jeżeli formularz nie może przyjąć pustej wartości, samo sprawdzenie tekst != null nie wystarczy.

if (string.IsNullOrWhiteSpace(wartosc))
{
    Console.WriteLine("Pole nie zawiera użytecznej treści.");
}

Ta metoda uznaje za puste także wartości składające się wyłącznie ze spacji, tabulatorów lub znaków nowej linii. W walidacji formularzy jest zwykle trafniejsza niż samo IsNullOrEmpty.

Jak myśleć o stringu w codziennym kodzie

Najprostsza zasada brzmi: używaj string do gotowych wartości tekstowych, interpolacji do czytelnego składania komunikatów, a StringBuilder do intensywnego budowania dużych danych. Przy porównywaniu zawsze zdecyduj, czy ważna jest wielkość liter i kultura.

Trzeba też pamiętać, że tekst ma dwa oblicza. Dla programu jest sekwencją jednostek UTF-16, ale dla użytkownika jest językiem, znakami i znaczeniem. Dobre rozwiązanie uwzględnia oba poziomy, szczególnie gdy aplikacja obsługuje emoji, wiele języków albo dane wprowadzane z zewnątrz.

Jeśli te rozróżnienia są jasne, string przestaje być tylko prostym typem do przechowywania napisów. Staje się przewidywalnym narzędziem do pracy z komunikatami, konfiguracją, danymi wejściowymi i tekstem generowanym przez aplikację.

FAQ - Najczęstsze pytania

Operacja taka jak tekst += " nowy fragment" tworzy nowy napis zamiast modyfikować istniejący obiekt. Przy kilku połączeniach nie ma to większego znaczenia, ale podczas wielokrotnych zmian w pętli warto użyć StringBuilder, aby ograniczyć kopiowanie danych i tworzenie pośrednich obiektów.

Pusty napis "" istnieje i ma długość zero, natomiast null oznacza brak obiektu. Tekst składający się wyłącznie ze spacji, tabulatorów lub znaków nowej linii nie jest pustym napisem, dlatego przy walidacji formularzy najlepiej użyć string.IsNullOrWhiteSpace.

Dla loginów, tokenów, kluczy i innych danych technicznych należy zwykle użyć StringComparison.Ordinal albo OrdinalIgnoreCase, ponieważ wynik nie zależy od ustawień regionalnych. Przy sortowaniu i prezentowaniu tekstu użytkownikowi może być właściwsze porównanie kulturowe, na przykład CurrentCulture.

Właściwość Length zwraca liczbę jednostek UTF-16, a nie zawsze liczbę znaków widocznych dla człowieka. Niektóre emoji, takie jak "😀", zajmują dwie jednostki char, więc przy limitach tekstu dla użytkownika trzeba czasem uwzględnić punkty kodowe lub grafemy.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi

string
stringbuilder
interpolacja
unicode
null
Autor Radosław Krajewski
Radosław Krajewski
Nazywam się Radosław Krajewski i od 6 lat zgłębiam tajniki programowania .NET, chmury Azure oraz sztucznej inteligencji. Moja przygoda z tymi technologiami zaczęła się od fascynacji tym, jak złożone problemy można rozwiązywać za pomocą kodu i innowacyjnych narzędzi. Staram się przekazywać tę wiedzę w sposób zrozumiały, dzieląc się swoimi doświadczeniami i spostrzeżeniami na kursdotnet.pl. W moich artykułach skupiam się na praktycznych aspektach, porównuję różne rozwiązania i analizuję najnowsze trendy, aby dostarczyć Wam rzetelne i aktualne informacje, które pomogą Wam rozwijać się w tej dynamicznie zmieniającej się dziedzinie.

Udostępnij artykuł

Napisz komentarz