Porównywanie stringów w C# — pułapka tureckiego I

Łańcuchy, czyli ciągi znaków w C#, mają typ string — sekwencję znaków char w standardzie Unicode. Słowo kluczowe string jest aliasem do System.String — oba zapisy są równoważne. Ten artykuł skupia się na jednym, często pomijanym temacie: jak poprawnie porównywać stringi. Po pełną głębię niezmienności (immutability) i konsekwencje wydajnościowe zajrzyj do artykułu string w C# — czym jest i jak działa, a po komplet metod manipulacji tekstem (Replace, Split, Substring i inne) — do Manipulacji tekstem w C#.
Podstawy — tworzenie i indeksowanie
string napis = "Tekst";
// T e k s t
// indeks: 0 1 2 3 4
char a = napis[1]; // 'e'
int n = napis.Length; // 5Dlaczego == dla stringów porównuje treść, a nie referencję
string jest typem referencyjnym, ale w przeciwieństwie do zwykłych klas, operator == jest dla niego przeciążony tak, by porównywał zawartość, a nie adres w pamięci — dokładnie tak, jak intuicyjnie oczekujesz:
string a = "test";
string b = new string("test".ToCharArray());
Console.WriteLine(a == b); // true — porównanie treści
Console.WriteLine(ReferenceEquals(a, b)); // false — to dwa różne obiekty w pamięciTo wyjątek, o którym warto pamiętać — dla większości innych typów referencyjnych == domyślnie porównuje referencje, nie zawartość.
StringComparison — kultura kontra porządek znaków
Prosty operator == zawsze porównuje ordinalnie (bajt po bajcie kodu Unicode), z rozróżnieniem wielkości liter. To nie zawsze jest to, czego potrzebujesz — dlatego Equals i Compare przyjmują parametr StringComparison:
string a = "Kraków";
string b = "KRAKÓW";
a.Equals(b); // false — różne wielkości liter
a.Equals(b, StringComparison.OrdinalIgnoreCase); // true — ignoruje wielkość liter, bez wpływu kultury
a.Equals(b, StringComparison.CurrentCultureIgnoreCase); // true, ale wynik zależy od bieżącej kultury systemu- Ordinal / OrdinalIgnoreCase — porównanie binarne, niezależne od ustawień regionalnych. Wybierz do porównań technicznych: kluczy, nazw plików, identyfikatorów, adresów URL, haseł.
- CurrentCulture / CurrentCultureIgnoreCase — porównanie z uwzględnieniem reguł językowych bieżącej kultury systemu (kolejność alfabetyczna, znaki diakrytyczne). Wybierz do sortowania i porównań widocznych dla użytkownika w interfejsie.
- InvariantCulture / InvariantCultureIgnoreCase — reguły językowe niezależne od ustawień systemu, ale wciąż “kulturowe” (inne niż czysto binarne Ordinal). Rzadziej potrzebne niż dwie powyższe opcje.
Pułapka: turecki problem z literą “i”
To klasyczny, realny bug, który pokazuje, dlaczego wybór StringComparison ma znaczenie. W kulturze tureckiej wielka litera odpowiadająca małemu i to İ (z kropką), a nie zwykłe I:
string status = "INACTIVE";
// Na maszynie z turecką kulturą systemową ToUpper()/ToLower() bez parametru
// używa CurrentCulture — "inactive".ToUpper() może NIE dać "INACTIVE"!
bool czyNieaktywny = status.ToUpper() == "INACTIVE"; // ryzykowne — zależy od kultury systemu
// Bezpieczne: porównanie niezależne od kultury
bool bezpieczne = string.Equals(status, "INACTIVE", StringComparison.OrdinalIgnoreCase);Dla porównań technicznych (kody statusów, klucze konfiguracyjne, stałe w kodzie) zawsze używaj wariantu Ordinal — to jednocześnie bezpieczniejsze i szybsze niż warianty kulturowe, bo pomija cały mechanizm reguł językowych.
Compare() — porządkowanie, nie tylko równość
Gdy potrzebujesz nie tylko “czy równe”, ale też “co jest wcześniej w porządku sortowania”, użyj string.Compare() — zwraca liczbę ujemną, zero lub dodatnią:
int wynik = string.Compare("Ala", "Basia", StringComparison.Ordinal);
// wynik < 0 => "Ala" jest przed "Basia" w porządku sortowania
// wynik == 0 => równe
// wynik > 0 => "Ala" jest po "Basia"To dokładnie ten mechanizm, na którym opierają się domyślne przeciążenia List<T>.Sort() i LINQ OrderBy() dla stringów.
Szybka ściąga: Join, Concat, ToCharArray
string.Join(separator, elementy)— łączy elementy kolekcji w jeden łańcuch, z separatorem między nimi.string.Concat(...)— skleja łańcuchy bez separatora (odpowiednik operatora+).napis.ToCharArray()— kopiuje znaki do tablicychar[], gdy potrzebujesz API operującego na tablicach.
string[] slowa = { "Ala", "ma", "kota" };
string zdanie = string.Join(" ", slowa); // "Ala ma kota"Jeśli chcesz zrozumieć, dlaczego string w C# zachowuje się tak, a nie inaczej — referencje, niezmienność, stack vs heap — zobacz string w C# — czym jest i jak działa.
FAQ
Której opcji StringComparison użyć domyślnie?
Jeśli nie masz konkretnego powodu, by uwzględniać kulturę użytkownika, wybieraj Ordinal/OrdinalIgnoreCase — jest przewidywalny, szybki i bezpieczny niezależnie od tego, na jakim systemie działa aplikacja.
Czy == i Equals() dla stringów to to samo?
Domyślnie tak — oba porównują treść ordinalnie. Różnica pojawia się dopiero, gdy jawnie podasz StringComparison do Equals(), czego operator == nie obsługuje.
🚀 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.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
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ę →
2 comments