🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Efektywne manipulowanie stringiem w C#

Łańcuchy w C# są niezmienne (immutable) — każda operacja na string tworzy nowy obiekt, zamiast modyfikować istniejący (pełne omówienie niezmienności w artykule Łańcuchy string w C#). Przy pojedynczej operacji to bez znaczenia. Przy tysiącach operacji w pętli — to realny, mierzalny koszt wydajnościowy. Ten artykuł pokazuje, dlaczego tak jest i jakie masz opcje poza oczywistym StringBuilder.

Dlaczego sklejanie w pętli to O(n²), a nie O(n)

Gdy sklejasz łańcuch operatorem + lub += w pętli, każda iteracja tworzy nowy obiekt string i kopiuje do niego całą dotychczasową zawartość plus nowy fragment:

// ❌ Kosztowne: każda iteracja kopiuje CAŁY dotychczasowy tekst
string wynik = "";
for (int i = 0; i < 1000; i++)
{
    wynik += i + ", ";   // nowy obiekt string, kopia wszystkiego dotychczas + nowy fragment
}

Przy n iteracjach kopiujesz średnio połowę rosnącego tekstu za każdym razem — sumarycznie to 1 + 2 + 3 + … + n skopiowanych znaków, czyli w przybliżeniu n²/2. Dla 1000 iteracji to różnica między ~1000 operacjami (liniowo) a ~500 000 operacjami kopiowania (kwadratowo). Przy 10 000 iteracji różnica robi się już dotkliwa — kwadratowy wzrost oznacza, że dziesięciokrotnie więcej danych to sto razy więcej pracy.

StringBuilder — rozwiązanie O(n)

StringBuilder (z System.Text) modyfikuje bufor znaków w miejscu, zamiast tworzyć nowy obiekt przy każdej zmianie — dopisanie tekstu jest średnio O(1), a cała pętla n dopisań to O(n), nie O(n²):

using System.Text;

var sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i).Append(", ");
}
string wynik = sb.ToString();

Pełny przegląd API StringBuilder (AppendInsertRemoveReplace, pojemność, method chaining) znajdziesz w dedykowanym artykule: StringBuilder w C#. Tutaj skupiamy się na tym, kiedy w ogóle po niego sięgać — i jakie ma alternatywy.

Gdy znasz liczbę elementów z góry: string.Join / string.Concat

Jeśli łączysz gotową kolekcję elementów (a nie budujesz tekst przyrostowo w logice warunkowej), string.Join bywa czytelniejszy i równie wydajny co StringBuilder — wewnętrznie oblicza całkowitą potrzebną długość raz, przed alokacją:

var liczby = Enumerable.Range(0, 1000).Select(i => i.ToString());

// Czytelniejsze niż ręczna pętla ze StringBuilder, podobnie wydajne
string wynik = string.Join(", ", liczby);

string.Concat(a, b, c, d) ma podobną przewagę dla stałej, znanej z góry liczby fragmentów — kompilator C# tłumaczy zresztą zwykłe sklejanie kilku literałów operatorem + właśnie na wywołanie string.Concat, więc "a" + "b" + "c" poza pętlą nie ma problemu wydajnościowego opisanego wyżej — problem pojawia się dopiero, gdy liczba sklejeń rośnie w pętli.

ReadOnlySpan<char> — operacje bez żadnej alokacji

Metody takie jak Substring() zawsze alokują nowy obiekt string na stercie — nawet jeśli tylko “podglądasz” fragment tekstu i zaraz go odrzucasz (np. podczas parsowania). ReadOnlySpan<char> pozwala operować na fragmencie istniejącego łańcucha bez kopiowania danych:

string linia = "2026-07-13,Warszawa,22.5";

ReadOnlySpan<char> span = linia;
int pierwszyPrzecinek = span.IndexOf(',');

// Podgląd fragmentu bez alokacji nowego stringa
ReadOnlySpan<char> data = span[..pierwszyPrzecinek];

Console.WriteLine(data.ToString()); // "2026-07-13" — kopia powstaje dopiero tutaj, świadomie

To technika dla gorących ścieżek wykonania (hot paths) — parsowania dużych plików, logów, protokołów sieciowych — gdzie tysiące wywołań Substring() zapychałyby stertę zbędnymi alokacjami. Dla zwykłego kodu biznesowego różnica jest zwykle niemierzalna, a czytelność zwykłego Substring() wygrywa.

String interning — gdy ten sam tekst pojawia się wielokrotnie

CLR automatycznie “internuje” literały tekstowe znane w czasie kompilacji — dwa identyczne literały "abc" w kodzie wskazują w pamięci na ten sam obiekt, nie na dwie kopie:

string a = "test";
string b = "test";
Console.WriteLine(ReferenceEquals(a, b)); // true — ten sam obiekt w pamięci

Stringi zbudowane dynamicznie (np. przez StringBuilder.ToString() albo konkatenację zmiennych) nie są automatycznie internowane — każdy taki string to osobny obiekt, nawet jeśli treść jest identyczna z innym. Jeśli masz aplikację, która wielokrotnie tworzy te same, powtarzające się teksty w pamięci (np. parsując duży plik z powtarzającymi się wartościami), możesz ręcznie je zinternować metodą string.Intern(s), żeby zredukować zużycie pamięci kosztem odrobiny narzutu na wyszukanie w puli. To rzadko potrzebna, wąsko wyspecjalizowana optymalizacja — warto o niej wiedzieć, ale nie stosować profilaktycznie.

Pełny przegląd metod StringBuilder — deklaracja, Append, method chaining i pułapkę porównywania przez Equals() — znajdziesz w StringBuilder w C# — kiedy używać i jak działa.

Podsumowanie — co wybrać

  • Kilka sklejeń poza pętlą — zwykła interpolacja $"..." lub +. Kompilator i tak zoptymalizuje to do string.Concat.
  • Budowanie tekstu w pętli, warunkowo, przyrostowo — StringBuilder.
  • Łączenie gotowej kolekcji elementów znaną liczbą razy — string.Join.
  • Parsowanie dużych ilości tekstu w gorącej ścieżce — ReadOnlySpan<char>, żeby uniknąć zbędnych alokacji Substring().
  • Powtarzające się, identyczne stringi budowane dynamicznie w pamięciożernej aplikacji — rozważ string.Intern, ale dopiero gdy profiler faktycznie wskaże to jako problem.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

2 comments

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →