Manipulacja tekstem w C# — Replace, Split, Substring inne metody string
Każda aplikacja przetwarza tekst. Formularze, raporty, importy CSV, logi, API — wszędzie napotykasz dane w postaci stringów, które trzeba oczyścić, wyciąć, zamieniać i formatować. Jeśli nie opanujesz manipulacji tekstem w C#, będziesz co chwilę pisać te same niezgrabne pętle i tracić czas na debugowanie błędów, których nie rozumiesz.
W tym tutorialu poznasz 10 kluczowych metod stringowych w C# — od ToUpper() przez Replace(), Substring(), Contains(), Split() aż po PadLeft(). Nie będzie tu definicji z dokumentacji. Będzie konkretny kod, realistyczne scenariusze i — co ważniejsze — wyjaśnienie dlaczego coś działa tak, a nie inaczej.
📌 Czego nauczysz się w tym artykule?
✅ Jak bezpiecznie normalizować tekst (ToUpper, ToLower, StringComparison)
✅ Jak zamieniać i wycinać fragmenty stringów (Replace, Remove, Insert, Substring)
✅ Jak parsować dane CSV i unikać ukrytych pułapek (Split + StringSplitOptions)
✅ Jak sprawdzać zawartość stringa (Contains, StartsWith, EndsWith)
✅ Jak formatować wyrównane kolumny w konsoli (PadLeft, PadRight)
✅ Jak zrefaktoryzować powtarzający się kod do reużywalnej metody
✅ Dlaczego string jest niemutowalny i co to oznacza dla Twojego kodu
Zanim zaczniesz: najważniejszy model mentalny pracy ze stringami
Zanim przejdziemy do konkretnych metod, musisz wiedzieć jedną rzecz. To zdanie warto zapamiętać:
String w C# jest niemutowalny (immutable).
Żadna metoda stringowa nie zmienia oryginalnego tekstu. Każda operacja zwraca nowy string.
Jeśli nie przypiszesz wyniku do zmiennej — efekt znika.
Analogia: kartka papieru

Wyobraź sobie kartkę z napisem “jan”. Kiedy wywołujesz ToUpper(), nie wymazujesz liter na tej kartce — tworzysz nową kartkę z napisem “JAN”. Oryginalna kartka leży nienaruszono, jeśli nie przypisałeś wyniku.
string imie = "jan";
// ❌ BŁĄD: wynik jest ignorowany — imie nadal = "jan"
imie.ToUpper();
Console.WriteLine(imie); // "jan"
// ✅ POPRAWNIE: zawsze przypisuj wynik
imie = imie.ToUpper();
Console.WriteLine(imie); // "JAN"
⚠️ Najczęstszy błąd początkujących
Wywołanie metody stringowej bez przypisania wyniku to jeden z najczęstszych błędów.
Kod się kompiluje, program działa — ale tekst się nie zmienia. I to jest pułapka.
Złota reguła: zmienna = zmienna.Metoda(…)
ToUpper() i ToLower() — normalizacja tekstu
Kiedy porównujesz dane od użytkownika, nigdy nie wiesz, jaką wielkość liter wybierze. Dlatego przed porównaniem zawsze normalizujesz tekst do jednego formatu.
Podstawowe użycie
string input = "Jan Kowalski";
// Konwersja na wielkie litery
Console.WriteLine(input.ToUpper()); // "JAN KOWALSKI"
// Konwersja na małe litery
Console.WriteLine(input.ToLower()); // "jan kowalski"
// Bezpieczniejsza alternatywa dla aplikacji wielojęzycznych
Console.WriteLine(input.ToUpperInvariant());
Kiedy użyć ToUpperInvariant() zamiast ToUpper()?
Metoda ToUpper() używa ustawień kulturowych systemu. W języku tureckim litera “i” zamieniona na wielką daje “İ” (z kropką nad literą), a nie “I”. Jeśli Twoja aplikacja działa globalnie lub porównujesz kody systemowe — zawsze używaj InvariantCulture.
// ❌ Potencjalnie niebezpieczne przy locale=tr-TR
bool match = input.ToLower() == "admin";
// ✅ Bezpieczne niezależnie od ustawień systemu
bool match = string.Equals(input, "admin", StringComparison.OrdinalIgnoreCase);
💡 Pro tip
Do porównań stringów w C# używaj string.Equals(a, b, StringComparison.OrdinalIgnoreCase) zamiast a.ToLower() == b.ToLower().
Jest czytelniej, bezpieczniej i szybciej.
Replace(), Remove() i Insert() — chirurgiczne operacje na stringu
Te trzy metody pozwalają modyfikować konkretne fragmenty tekstu. Każda z nich robi co innego — i każda ma swoją pułapkę.
Replace() — zamień wszystkie wystąpienia
string tekst = "Witaj, świecie!";
// Zamień wszystkie 'i' na 'I'
string wynik = tekst.Replace("i", "I");
// "WItaj, świecIe!"
// Method chaining — każdy Replace() zwraca nowy string
string telefon = "+48-123-456-789";
telefon = telefon.Replace("-", "").Replace("+48", "");
// "123456789"
Remove() — wytnij fragment od indeksu
string tekst = "Witaj, świecie!";
// Remove(startIndex, count) — usuń 9 znaków od indeksu 6
string wynik = tekst.Remove(6, 9);
// "Witaj, !"
// ⚠️ Uwaga: indeksy zaczynają się od ZERA
// ArgumentOutOfRangeException jeśli startIndex + count > Length
Insert() — wstaw tekst w konkretnym miejscu
string tekst = "Witaj, świecie!";
// Insert(index, value) — wstaw na pozycji 7
string wynik = tekst.Insert(7, "piękny ");
// "Witaj, piękny świecie!"
⚠️ Pułapka: ArgumentOutOfRangeException
Metody Remove() i Insert() rzucają wyjątek, jeśli podasz indeks poza zakresem stringa.
Zawsze waliduj dane wejściowe zanim wywołasz te metody na danych od użytkownika.
Pamiętaj: indeksy w C# zaczynają się od ZERA, nie od 1.
Substring() — wytnij dokładnie to, czego potrzebujesz

Substring to Twoje narzędzie do wycinania fragmentów tekstu. Możesz podać indeks startowy i opcjonalnie długość — albo zabrać wszystko do końca.
Podstawowa składnia i przykłady
string email = "jan.kowalski@firma.pl";
// indeksy zaczynają się od 0!
// Weź 3 znaki od indeksu 0
string imie = email.Substring(0, 3); // "jan"
// Weź wszystko od indeksu 13 do końca
string domena = email.Substring(13); // "firma.pl"
// C# 8+ — nowoczesna alternatywa: range syntax
string domenaNew = email[13..]; // "firma.pl" — to samo
string imieNew = email[..3]; // "jan" — to samo co Substring(0, 3)
Dynamiczne indeksy — użyj IndexOf()
Gdy indeks nie jest stały (np. nie wiesz, gdzie jest znak “@” w emailu), połącz Substring() z IndexOf():
string email = "jan.kowalski@firma.pl";
// Znajdź pozycję znaku '@'
int atIndex = email.IndexOf('@'); // 12
// Wytnij nazwę użytkownika (wszystko przed '@')
string user = email.Substring(0, atIndex); // "jan.kowalski"
// Wytnij domenę (wszystko po '@')
string domain = email.Substring(atIndex + 1); // "firma.pl"
💡 Pro tip
IndexOf() zwraca -1 jeśli szukany znak nie istnieje w stringu.
Zawsze sprawdzaj: if (atIndex >= 0) przed wywołaniem Substring(), inaczej ryzykujesz ArgumentOutOfRangeException.
Contains() — sprawdź, czy tekst w ogóle tam jest
Contains() odpowiada na proste pytanie: czy dany fragment tekstu w ogóle istnieje w stringu? Zwraca true lub false.
string log = "ERROR: Nie można połączyć z bazą danych";
// Sprawdź czy log zawiera słowo "ERROR"
if (log.Contains("ERROR"))
{
Console.WriteLine("Znaleziono błąd krytyczny!");
}
// Case-insensitive (bez rozróżniania wielkości liter) — C# 5+
bool haserror = log.Contains("error", StringComparison.OrdinalIgnoreCase); // true
Powiązane metody: StartsWith() i EndsWith()
string url = "https://firma.pl/kontakt";
// Sprawdź prefix
bool isHttps = url.StartsWith("https"); // true
// Sprawdź suffix
bool isPl = url.EndsWith(".pl/kontakt"); // true
// Case-insensitive
url.StartsWith("HTTPS", StringComparison.OrdinalIgnoreCase); // true
⚠️ Anty-wzorzec: nie używaj Replace() do walidacji
Kuszące, ale złe: email.Replace(“@”, “”) != email (żeby sprawdzić, czy jest małpa)
Poprawnie: email.Contains(“@”)
Replace() jako walidator jest wolniejszy, nieczytelny i łatwy do pomylenia.
Split() — podziel string na tablicę
Split to Twój główny parser do CSV, ścieżek, list rozdzielanych przecinkami i wszystkiego, co ma separator. Zwraca tablicę string[].
Podstawowe użycie
string csv = "Jan,Kowalski,Warszawa,35";
// Podziel po przecinku
string[] pola = csv.Split(',');
Console.WriteLine(pola[0]); // "Jan"
Console.WriteLine(pola[1]); // "Kowalski"
Console.WriteLine(pola[2]); // "Warszawa"
Console.WriteLine(pola[3]); // "35"
// Podział po wielu separatorach jednocześnie
string tekst = "jabłko;gruszka,śliwka banana";
string[] owoce = tekst.Split(';', ',', ' ');
StringSplitOptions — dla brudnych danych
string csv = "Jan, Kowalski,,Warszawa";
// ❌ Bez opcji — spacje i puste elementy trafiają do tablicy
string[] zle = csv.Split(',');
// ["Jan", " Kowalski", "", "Warszawa"]
// ^^spacja ^^pusty element!
// ✅ Z opcjami — usuń puste, przytnij spacje
string[] dobre = csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); // C# 8+
// ["Jan", "Kowalski", "Warszawa"]
⚠️ Dwie ukryte pułapki Split()
Pułapka 1: Spacja przy separatorze — “Jan, Kowalski” da pola[1] = ” Kowalski” (z wiodącą spacją).
Rozwiązanie: użyj StringSplitOptions.TrimEntries (C# 8+) lub wywołaj .Trim() na każdym elemencie.Pułapka 2: Pusty string — “”.Split(‘,’) NIE zwraca pustej tablicy.
Zwraca tablicę z jednym pustym elementem: [“”]. Rozwiązanie: StringSplitOptions.RemoveEmptyEntries.

PadLeft() i PadRight() — formatowanie wyrównanych kolumn
Metody Pad uzupełniają string do zadanej długości, dodając znaki wypełnienia. Brzmią niepozornie, ale są nieocenione przy budowaniu raportów, tabel tekstowych i numeracji.
string numer = "42";
// PadLeft — uzupełnij zerami od lewej (numery faktur!)
Console.WriteLine(numer.PadLeft(6, '0')); // "000042"
// PadRight — uzupełnij myślnikami od prawej
Console.WriteLine(numer.PadRight(6, '-')); // "42----"
// Wyrównana tabela w konsoli:
Console.WriteLine(
"Jan".PadRight(12) +
"Kowalski".PadRight(15) +
"jan@firma.pl".PadRight(30) +
"123456789".PadLeft(12)
);
// "Jan Kowalski jan@firma.pl 123456789"
💡 Pro tip
Jeśli string jest już dłuższy niż żądana długość — Pad* zwraca oryginał bez skracania.
PadLeft(‘0’) to standardowy sposób na numery z wiodącymi zerami (faktury, identyfikatory).
Przykład produkcyjny — czyszczenie danych z importu CSV
Zobaczmy wszystkie metody razem w jednym, realistycznym scenariuszu: czyszczenie brudnego rekordu z zewnętrznego systemu.
Dane wejściowe — typowy brudny import
// Rekord z zewnętrznego systemu — pełen problemów:
string rekord = " jan,KOWALSKI,jan.kowalski@FIRMA.pl,+48-123-456-789 ";
// ^^spacje ^^wielkie litery ^^myślniki ^^spacje
Krok po kroku — od brudnych danych do gotowego raportu
// Krok 1: Trim — usuń spacje z brzegów (zawsze pierwszy krok!)
string oczyszczony = rekord.Trim();
// Krok 2: Split — podziel na pola
string[] pola = oczyszczony.Split(',');
// Krok 3: Normalize — imię i nazwisko z wielkiej litery
string imie = Capitalize(pola[0]); // "Jan"
string nazwisko = Capitalize(pola[1]); // "Kowalski"
// Krok 4: ToLower — emaile zawsze normalizujemy do małych liter
string email = pola[2].ToLower(); // "jan.kowalski@firma.pl"
// Krok 5: Replace chain — wyczyść numer telefonu
string telefon = pola[3]
.Replace("-", "") // usuń myślniki
.Replace("+48", ""); // usuń prefiks
// "123456789"
// Krok 6: Contains + PadRight — formatuj raport
bool czyFirmowy = email.Contains("@firma.pl");
string etykieta = czyFirmowy ? "FIRMOWY" : "ZEWNĘTRZNY";
Console.WriteLine(
imie.PadRight(12) +
nazwisko.PadRight(15) +
email.PadRight(30) +
telefon.PadLeft(12) +
$" [{etykieta}]"
);
Refaktoryzacja — wyciągnij powtarzającą się logikę
Normalizacja imienia i nazwiska to ta sama logika powtórzona dwa razy. Wyciągnij ją do metody — będzie reużywalna i testowalna:
// Metoda pomocnicza: pierwsza litera wielka, reszta mała
static string Capitalize(string input)
{
// ⚠️ Zawsze zabezpieczaj się przed null i pustym stringiem
if (string.IsNullOrEmpty(input)) return input;
return char.ToUpper(input[0]) + input.Substring(1).ToLower();
// C# 8+: char.ToUpper(input[0]) + input[1..].ToLower();
}
✅ Po refaktoryzacji: Capitalize() jest reużywalna dla imienia, nazwiska i każdego kolejnego pola.
✅ Jeden punkt zmiany: jeśli logika się zmieni, poprawiasz w jednym miejscu.
✅ Łatwa do testowania: możesz napisać unit test dla Capitalize() niezależnie od reszty kodu.
Checklista: manipulacja tekstem w C# — co musisz wiedzieć
Zanim przejdziesz dalej, upewnij się, że rozumiesz każdy z poniższych punktów:

✅ String jest niemutowalny — zawsze przypisuję wynik: zmienna = zmienna.Metoda()
✅ ToUpper/ToLower normalizuje tekst przed porównaniem
✅ Do porównań case-insensitive używam StringComparison.OrdinalIgnoreCase
✅ Replace(), Remove(), Insert() — każda zwraca NOWY string
✅ Indeksy w C# zaczynają się od ZERA
✅ Substring() + IndexOf() = dynamiczne wycinanie bez magicznych liczb
✅ Contains() to prawidłowe narzędzie do sprawdzania obecności — nie Replace()
✅ Split() z StringSplitOptions.TrimEntries i RemoveEmptyEntries dla brudnych danych
✅ PadLeft/PadRight nie skraca stringa — tylko uzupełnia do zadanej długości

Zobacz też — powiązane artykuły
👉 Tworzenie klas i obiektów w C# — kompletny przewodnik
Zadanie praktyczne — sprawdź się!
Teoria bez praktyki nic nie daje. Oto dwa zadania — jedno obowiązkowe, jedno dla ambitnych.
Zadanie 1 — parser emaila (15–30 minut)
Napisz program konsolowy, który:
- Pobiera od użytkownika adres email za pomocą Console.ReadLine()
- Sprawdza, czy zawiera znak “@” — jeśli nie, wypisuje błąd
- Rozdziela email na część przed i po “@” (użyj IndexOf + Substring lub Split)
- Normalizuje email do małych liter
- Wypisuje: Użytkownik: jan.kowalski | Domena: firma.pl
Zadanie 2 (dla ambitnych) — formater numeru telefonu
Napisz metodę static string FormatujTelefon(string numer), która:
- Usuwa wszystkie znaki niebędące cyframi (myślniki, spacje, nawiasy, “+”)
- Jeśli zostaje 9 cyfr — formatuje jako XXX-XXX-XXX
- Jeśli zostaje 11 cyfr zaczynających się od “48” — usuwa prefiks i formatuje jak wyżej
- W każdym innym przypadku zwraca oryginał z dopiskiem ” (nieznany format)”
- Przetestuj na: +48-123-456-789, (123) 456 789, 48123456789, 123456789, abc
🔑 Złota reguła do zapamiętania na zawsze:
String jest niemutowalny. Zawsze przypisuj wynik: zmienna = zmienna.Metoda(…)

