String — tekst, którego nie da się zmienić
Uczeń wpisuje nazwę z dodatkowymi spacjami i wielkimi literami. Program ma to uporządkować — a przy okazji nauczysz się reguły, o którą potyka się każdy początkujący.
Czego się dziś nauczysz
- Wyjaśnisz, dlaczego metoda tekstowa nie zmienia napisu, na którym ją wywołano
- Oczyścisz i przekształcisz napis metodami
Trim,ReplaceiToUpper - Odnajdziesz fragment metodami
Contains,StartsWithiIndexOf - Wytniesz część napisu metodą
Substring, nie wychodząc poza zakres - Porównasz napisy tak, żeby wielkość liter miała albo nie miała znaczenia — świadomie
Przygotowanie: lekcje 01–07. Przewidywany czas: 45–90 minut z zadaniami. Przykłady wymagają .NET 8 lub nowszego, z włączonymi ImplicitUsings i Nullable.
Napisu nie da się zmienić
To zaskakuje każdego: w C# żadna metoda nie zmienia istniejącego napisu. Każda tworzy nowy i zwraca go jako wynik. Napis raz utworzony pozostaje taki na zawsze — mówimy, że string jest niemutowalny.
string tekst = " Ala ";
tekst.Trim(); // wynik POWSTAJE i natychmiast ginie
Console.WriteLine($"[{tekst}]"); // [ Ala ] - bez zmian!
tekst = tekst.Trim(); // TAK: przypisujemy wynik z powrotem
Console.WriteLine($"[{tekst}]"); // [Ala]
[ Ala ]
[Ala]
Najczęstszy błąd w tej lekcji
Wywołanie tekst.Trim();, tekst.Replace(…); albo tekst.ToUpper(); bez przypisania nie robi nic widocznego. Kompilator nie protestuje — kod jest poprawny, tylko bezużyteczny. Jeśli napis „się nie zmienia”, w dziewięciu przypadkach na dziesięć brakuje właśnie tekst = .
Można też zapisać wynik do innej zmiennej — wtedy masz obie wersje naraz:
string oryginal = " Ala ";
string czysty = oryginal.Trim();
Console.WriteLine($"[{oryginal}]"); // [ Ala ] - nietkniety
Console.WriteLine($"[{czysty}]"); // [Ala]
Po co taka niewygoda
Niezmienność ma swoje zalety: napis można bezpiecznie przekazywać w wiele miejsc programu, mając pewność, że nikt go po drodze nie podmieni. Ceną jest koszt — sklejanie tysięcy napisów w pętli tworzy tysiące obiektów. Rozwiązanie tego problemu, klasa StringBuilder, czeka w lekcji 29.
Czyszczenie, zamiana, wielkość liter
string wpis = " Ala Kowalska ";
Console.WriteLine($"[{wpis.Trim()}]"); // [Ala Kowalska]
Console.WriteLine($"[{wpis.TrimStart()}]"); // [Ala Kowalska ]
Console.WriteLine($"[{wpis.TrimEnd()}]"); // [ Ala Kowalska]
string zdanie = "kot i kot";
Console.WriteLine(zdanie.Replace("kot", "pies")); // pies i pies
Console.WriteLine(zdanie.ToUpper()); // KOT I KOT
Console.WriteLine(zdanie.ToLower()); // kot i kot
Console.WriteLine(zdanie); // kot i kot - oryginal caly czas ten sam
Zwróć uwagę na dwie rzeczy. Trim usuwa białe znaki wyłącznie ze skrajów — spacja między imieniem a nazwiskiem zostaje. Replace zamienia wszystkie wystąpienia, nie tylko pierwsze.
ToUpper i litera „i”
W języku tureckim wielką literą od i jest İ, a nie I. Program uruchomiony na komputerze z tureckimi ustawieniami regionalnymi da inny wynik ToUpper niż twój. Gdy zamieniasz wielkość liter po to, żeby porównać techniczne kody — start, STOP, nazwy plików — używaj wersji ToUpperInvariant albo, jeszcze lepiej, porównania z StringComparison opisanego niżej.
Szukanie fragmentów i wycinanie
string plik = "raport-2026.txt";
Console.WriteLine(plik.Contains("2026")); // True
Console.WriteLine(plik.StartsWith("raport")); // True
Console.WriteLine(plik.EndsWith(".txt")); // True
Console.WriteLine(plik.IndexOf('-')); // 6 - pozycja pierwszego myslnika
Console.WriteLine(plik.IndexOf("2026")); // 7
Console.WriteLine(plik.IndexOf("csv")); // -1 - nie znaleziono
Console.WriteLine(plik.Substring(7)); // 2026.txt - od indeksu do konca
Console.WriteLine(plik.Substring(7, 4)); // 2026 - 4 znaki od indeksu 7
Console.WriteLine(plik.Substring(0, 6)); // raport
| Metoda | Zwraca | Uwaga |
|---|---|---|
Contains(x) | bool | Czy fragment gdziekolwiek występuje. Rozróżnia wielkość liter. |
StartsWith(x) / EndsWith(x) | bool | Czy napis zaczyna się / kończy podanym fragmentem. |
IndexOf(x) | int | Pozycja pierwszego wystąpienia albo −1, gdy nie ma. LastIndexOf szuka od końca. |
Substring(i) | string | Wszystko od indeksu i do końca. |
Substring(i, n) | string | Dokładnie n znaków od indeksu i. Wyjście poza napis daje wyjątek. |
Minus jeden to nie pozycja
IndexOf przy braku fragmentu zwraca -1. Podanie tej wartości do Substring kończy się wyjątkiem ArgumentOutOfRangeException. Od lekcji 14 będziesz ten wynik sprawdzać instrukcją warunkową — na razie zapamiętaj, że taka pułapka istnieje.
Porównywanie napisów
Dwa napisy porównujemy operatorem == — i dla string porównuje on zawartość, a nie miejsce w pamięci. To wyjątek wśród typów złożonych, wygodny i celowy.
string a = "Ala";
string b = "Ala";
Console.WriteLine(a == b); // True - taka sama zawartosc
Console.WriteLine(a == "ala"); // False - wielkosc liter ma znaczenie
Gdy wielkość liter ma być nieistotna, nie kombinuj z ToLower — powiedz to wprost:
string wpis = " START ";
Console.WriteLine(string.Equals(wpis.Trim(), "start", StringComparison.OrdinalIgnoreCase));
// True
Console.WriteLine(string.IsNullOrEmpty("")); // True
Console.WriteLine(string.IsNullOrWhiteSpace(" ")); // True
Console.WriteLine(string.IsNullOrWhiteSpace("Ala")); // False
Tryb StringComparison | Kiedy używać |
|---|---|
Ordinal | porównanie numerów znaków — szybkie i przewidywalne; do kodów, nazw plików, identyfikatorów |
OrdinalIgnoreCase | to samo, ale bez rozróżniania wielkości liter — domyślny wybór dla komend i kodów |
CurrentCulture | reguły języka użytkownika — do porządkowania tekstu pokazywanego człowiekowi |
CurrentCultureIgnoreCase | jak wyżej, bez wielkości liter |
Puste, białe i „nic”
"" to napis pusty — istnieje, ale ma zero znaków. " " to napis z samymi spacjami — nie jest pusty, choć wygląda jak nic. null oznacza brak napisu w ogóle i próba wywołania na nim metody kończy się wyjątkiem. Metoda string.IsNullOrWhiteSpace obsługuje wszystkie trzy przypadki naraz; do null wrócisz w lekcji 34.
Trzy sposoby na złożenie tekstu
string imie = "Ala";
int punkty = 18;
// 1. sklejanie operatorem +
Console.WriteLine("Uczen " + imie + " ma " + punkty + " pkt");
// 2. interpolacja - czytelniejsza, ta uzywamy najczesciej
Console.WriteLine($"Uczen {imie} ma {punkty} pkt");
// 3. napis doslowny @ - ukosniki bez podwajania
Console.WriteLine(@"C:\dane\raport.txt");
Console.WriteLine("C:\\dane\\raport.txt"); // to samo, zapis zwykly
Uczen Ala ma 18 pkt
Uczen Ala ma 18 pkt
C:\dane\raport.txt
C:\dane\raport.txt
Przy sklejaniu operatorem + liczba zostaje automatycznie zamieniona na tekst. To wygoda, ale i pułapka znana z lekcji 09: "Wynik: " + 5 + 5 daje Wynik: 55, bo dodawanie idzie od lewej.
Znak i napis też można skleić — powstaje napis:
string kod = "A7B2";
char pierwszy = kod[0];
string opis = "Pierwszy znak: " + pierwszy;
Console.WriteLine(opis); // Pierwszy znak: A
Console.WriteLine(opis.Length); // 16
Dzielenie napisu na części
Napis z separatorami — "Ala;Ola;Jan" — rozbija się metodą Split. Jej wynikiem jest tablica, a tablice poznasz dopiero w lekcji 23. Dlatego Split wraz z całym warsztatem przetwarzania danych czeka na ciebie w lekcji 29.
Przykład: porządkowanie wpisu
Program dostaje niechlujny wpis i doprowadza go do porządku. Przewidź każdy wiersz wyniku, zanim uruchomisz.
string wpis = " raport-2026.TXT ";
Console.WriteLine($"oryginal : [{wpis}]");
Console.WriteLine($"dlugosc : {wpis.Length}");
Console.WriteLine();
string czysty = wpis.Trim();
Console.WriteLine($"po Trim : [{czysty}] dlugosc: {czysty.Length}");
string maly = czysty.ToLowerInvariant();
Console.WriteLine($"malymi : {maly}");
Console.WriteLine($"zaczyna sie od 'raport'? {maly.StartsWith("raport")}");
Console.WriteLine($"konczy sie na '.txt'? {maly.EndsWith(".txt")}");
Console.WriteLine($"pozycja myslnika {maly.IndexOf('-')}");
Console.WriteLine();
string rok = maly.Substring(7, 4);
string nazwa = maly.Substring(0, 6);
Console.WriteLine($"nazwa: {nazwa}, rok: {rok}");
Console.WriteLine($"nowa nazwa: {maly.Replace("2026", "2027")}");
Console.WriteLine();
Console.WriteLine($"oryginal nadal: [{wpis}]");
oryginal : [ raport-2026.TXT ]
dlugosc : 22
po Trim : [raport-2026.TXT] dlugosc: 15
malymi : raport-2026.txt
zaczyna sie od 'raport'? True
konczy sie na '.txt'? True
pozycja myslnika 6
nazwa: raport, rok: 2026
nowa nazwa: raport-2027.txt
oryginal nadal: [ raport-2026.TXT ]
Co dzieje się po kolei
- Nawiasy kwadratowe w wypisywaniu to zwykłe znaki — pokazują, gdzie kończy się napis, więc spacje przestają być niewidoczne. Warto używać ich zawsze przy testowaniu
Trim. - Każda operacja zwraca nowy napis i przypisujemy go do nowej zmiennej. Ostatni wiersz dowodzi, że
wpisprzez cały czas był nietknięty. ToLowerInvariant, a nieToLower— nazwa pliku to kod techniczny, więc nie chcemy zależeć od ustawień regionalnych komputera.- Liczby 7, 4, 0 i 6 w
Substringwpisano na sztywno, bo znamy budowę tego konkretnego napisu. W programie przyjmującym dowolne dane wyliczyłbyś je zIndexOf— po lekcji 14, gdy będziesz umiał sprawdzić wynik-1.
Zestawienie metod klasy string
| Metoda / właściwość | Zwraca | Działanie i ograniczenia |
|---|---|---|
tekst.Length | int | Liczba jednostek char. Właściwość — bez nawiasów. |
tekst[i] | char | Znak o indeksie i. Poza zakresem — wyjątek. |
tekst.Trim() | string | Nowy napis bez skrajnych białych znaków. Środka nie rusza. Są też TrimStart i TrimEnd. |
tekst.ToUpper() / ToLower() | string | Zmieniona wielkość liter według ustawień regionalnych. Wersje …Invariant — niezależne od nich. |
tekst.Replace(a, b) | string | Zamienia wszystkie wystąpienia. Podanie pustego b usuwa fragmenty. |
tekst.Contains(x) | bool | Czy fragment występuje. Rozróżnia wielkość liter, chyba że podasz StringComparison. |
tekst.StartsWith(x) / EndsWith(x) | bool | Sprawdzenie początku i końca napisu. |
tekst.IndexOf(x) | int | Pozycja pierwszego wystąpienia albo -1. LastIndexOf — od końca. |
tekst.Substring(i, n) | string | Fragment od indeksu i o długości n. Zły zakres — ArgumentOutOfRangeException. |
string.Equals(a, b, tryb) | bool | Porównanie według wskazanego trybu; radzi sobie z null. |
string.IsNullOrEmpty(t) | bool | true dla null i napisu pustego. |
string.IsNullOrWhiteSpace(t) | bool | Jak wyżej, a dodatkowo dla samych białych znaków. |
$"…{x}…" | string | Interpolacja — wstawia wartość wyrażenia. Pełne formatowanie w lekcji 13. |
@"…" | string | Napis dosłowny: ukośniki bez podwajania, wolno łamać wiersze. |
Zanim utkniesz
| Zapis | Problem |
|---|---|
tekst.Trim(); | Wynik ginie. Napisz tekst = tekst.Trim();. |
tekst[0] bez sprawdzenia długości | Pusty napis nie ma indeksu 0 — wyjątek. |
Substring(tekst.IndexOf("x")) | Gdy fragmentu nie ma, IndexOf daje -1 i Substring zgłasza wyjątek. |
a.ToLower() == b.ToLower() | Działa w większości przypadków, ale zależy od ustawień regionalnych i tworzy dwa zbędne napisy. Użyj string.Equals(a, b, StringComparison.OrdinalIgnoreCase). |
tekst == "" jako sprawdzenie „pusty” | Napis ze spacjami przejdzie ten test. Użyj string.IsNullOrWhiteSpace. |
"Suma: " + 2 + 3 | Da Suma: 23. Policz najpierw: "Suma: " + (2 + 3) albo użyj interpolacji. |
Dalszy ciąg tematu
Metody klasy string wystarczają do prostych zadań. Gdy tekst budujesz w pętli o wielu obiegach albo rozbijasz całe pliki danych, sięgniesz po StringBuilder i opcje metody Split z lekcji 29.
Zadania
Zapisz w zmiennej napis " Robot ". Wywołaj na nim Trim bez przypisania i wypisz zmienną między nawiasami kwadratowymi. Potem przypisz wynik i wypisz ponownie. Opisz w komentarzu, co się zmieniło i dlaczego.
Dla napisu "sprawozdanie-3b.docx" wypisz: długość, pozycję myślnika, pozycję kropki, sam rozszerzenie oraz nazwę bez rozszerzenia. Użyj IndexOf i Substring.
Sprawdź, czy wpis " START " po przycięciu jest komendą start bez względu na wielkość liter. Wypisz wynik dla tego wpisu oraz dla "restart" i " ". Nie używaj ToLower.
Dla czterech napisów: "", " ", "\t" i "Ala" wypisz w kolumnach ich Length, wynik IsNullOrEmpty i wynik IsNullOrWhiteSpace. Wyjaśnij różnicę między drugą a trzecią kolumną.
Z napisu "4417-2938-5561-0092" zbuduj wersję zamaskowaną "****-****-****-0092": pobierz cztery ostatnie znaki metodą Substring i doklej je do stałego przedrostka. Nie wpisuj końcówki na sztywno.
Mając trzy zmienne — imię, nazwisko i klasę — zbuduj i wypisz wizytówkę: pierwsza litera imienia i pełne nazwisko wielkimi literami (A. KOWALSKA), pod spodem klasa, a pod nią linia z tylu myślników, ile znaków ma najdłuższy z tych dwóch wierszy. Długość policz z Length, nie licz jej ręcznie.
Co trzeba zapamiętać
- Napis jest niezmienny — każda metoda zwraca nowy napis, a stary zostaje nietknięty.
- Bez przypisania wyniku (
tekst = tekst.Trim();) nic się nie zmieni. Trimdziała tylko na skrajach,Replacezamienia wszystkie wystąpienia.IndexOfzwraca-1, gdy nie znajdzie — nie podawaj tego wyniku doSubstring.- Dla
stringoperator==porównuje zawartość; wielkość liter ma znaczenie. - Porównania niezależne od wielkości liter zapisuj przez
string.EqualszStringComparison, nie przezToLower.
Dokumentacja: Microsoft Learn — temat tej lekcji.