Code review: porównywanie string’ów

Kod:

if(anyText.ToLower()=="tekst")
{
    Console.WriteLine("Zmienne takie same");   
}

Kod ma na celu sprawdzenie czy jakaś zmienna jest równa danemu strumieniowi znaków. Nie chcemy brać pod uwagę wielkości liter więc dlatego używamy funkcji ToLower. Zatem if zwróci true gdy anyText jest równy “tekst” lub “TEKST” itp.

Rozwiązanie ma jedną wadę – tworzony jest nowy, tymczasowy string po wywołaniu metody ToLower. W powyższym przykładzie będziemy mieli zatem 3 obiekty: anyText, obiekt dla “tekst” oraz obiekt dla ToLower. Wszystkie one muszą zostać potem zebrane przez GC. Jeśli mamy system czasu rzeczywistego dobrą praktyką jest optymalizacja takich rzeczy, zwłaszcza, że jest ona bardzo łatwa i wystarczy przestrzegać pewnych zasad. Lepszym rozwiązaniem jest zatem:

if(anyText.Equals("tekst",StringComparison.OrdinalIgnoreCase))
{
 Console.WriteLine("Zmienne takie same");   
}

Rozwiązanie prawie idealne – nie tworzymy już dodatkowej zmiennej pomocniczej. Mimo to, co w przypadku gdy anyText jest równy NULL? Oczywiście całość zakończy się wyjątkiem więc ktoś mógłby napisać:

if(anyText!=null&&anyText.Equals("tekst",StringComparison.OrdinalIgnoreCase))
{
    Console.WriteLine("Zmienne takie same");   
}

Wciąż brzydkie ponieważ da to się zapisać za pomocą jednego IF’a:

if(string.Equals(anyText,"tekst",StringComparison.OrdinalIgnoreCase))
{
 Console.WriteLine("Zmienne takie same");   
}

Warto zwrócić uwagę, że StringComparison przyjmuje kilka wartości:

1. Ordinal  – domyślna wartość. Znaki zamieniane są na wartości numeryczne (np. ASCII) i wtedy porównywane. Zdecydowanie najszybsza metoda jeśli chodzi o wydajność.

2. OrdinalIgnoreCase – tak jak wyżej ale ignorowana jest wielkość liter.

3. CurrentCulture – brana jest pod uwagę dana kultura. Zatem wynik może być różny w zależności od danej konfiguracji komputera (tzn. ustawień regionalnych).

4. InvariantCulture – niebrane są pod uwagę ustawienia regionalne – zawsze wynik będzie taki sam.

5. CurrentCultureIgnoreCase, InvariantCultureIgnoreCase – analogicznie jak wyżej z tym, że wielkość liter jest ignorowana.

W następnym poście opiszę dokładniej różnice między różnymi wartościami StringComparison bo jest tego trochę…

15 thoughts on “Code review: porównywanie string’ów”

  1. Ave!
    Pomięć o zabezpieczeniu się przed null-em to dobra rzecz, ale przekombinowałeś (trochę) niestety:

    Po prostu:
    if (“tekst”.Equals(anyText, StringComparison.OrdinalIgnoreCase))

    I żaden null nam niestraszny :]

    PS. Miłośników mikrooptymalizacji informuje że sprawdziłem przed chwilą, obydwie opcje są dokładnie tak samo wydajne.

  2. Pewnie, ze mozna ale nie lubie dawac wartosci po lewej stronie tak samo jak nie uzywam:
    if(null == object)
    {
    }
    To bylo dobre w CPP ale w c# nie trzeba juz tak. Chociaz jest to juz sprawa zalezna od danego programisty.

  3. A przy okazji mam pytanie.

    Czy zamiast
    if(string.IsNullOrEmpty(anyText)) {}
    mogę zrobić:
    if(anyText.Equals(string.Empty)) {}
    ?

    Bo tak zrobiłem i mimo iż anyText był pustym stringiem (zabezpieczenie przed nullem było wyżej), to nie wchodził mi do środka IF-a.

    Pozdrawiam.

  4. Nie chcę wchodzić w szczegółowość, ale akurat taki wyjątkowy przypadek mi był potrzebny. W każdym razie akurat nie działało mi to z string.Empty i Equals.

    Dzięki, pozdrawiam.

  5. Witaj,

    jestem pierwszy raz na blogu, trafiłem tu przez dotnetomaniak.pl. Więcej tego typu wpisów bo są naprawdę ciekawe :).

    Pozdrawiam

  6. Też osobiście wolę wersję MSM. Metody statyczne stringa zachowuję na wypadki typu IsNullOrEmpty() albo IsNullOrWhitespace().

    @Greg:
    if(anyText.Equals(string.Empty)) {}
    FxCop by się rzucał 😉 Zalecanym sposobem sprawdzania pustości nie-nullowego stringa jest bodajże użycie właściwości Length, tj.
    if (anyText.Length == 0) {}

  7. @LB:
    M nie odpowiada ta wersja poniewaz zostala tam uzyta wartosc tak jakby byla to zmienna a jest to tylko literal.

  8. To raczej kwestia gustu, do mnie bardziej przemawia przedstawione wcześniej rozwiązanie.

    Co do porównywania z pustym stringiem – zalecenie sprawdzania (nie)zerowości długości napisu różnego od null chyba jest jednym z tych drobnych, które warto zapamiętać, bo nie odpala całego porównywania związanego z lokalizacją (Culture) i zwyczajnie powinno być szybsze. Jakiś czas temu porzuciłem też sprawdzenia typu

    string str = …;
    if (str != null && str.Length > 0) {}

    na rzecz string.IsNullOrEmpty() (ew. IsNullOrWhitespace() wspomniane wcześniej). W tym kontekście stosowanie str.Length zamiast porównywania ze string.Empty ma mniejsze znaczenie, bo w większości zapewne będzie to oddelegowane do pomocniczych metod stringa.

  9. Ogólnie zgadzam się z przesłaniem posta. Ale trochę się poczepiam.

    “W powyższym przykładzie będziemy mieli zatem 3 obiekty: anyText, obiekt dla “tekst” oraz obiekt dla ToLower. Wszystkie one muszą zostać potem zebrane przez GC.”

    GC prawdopodobnie nigdy nie dotknie obiektu dla napisu “tekst”, gdyż referencja do tego string literal będzie w intern string poolu. [http://msdn.microsoft.com/en-us/library/aa691090%28v=vs.71%29.aspx]

    “Jeśli mamy system czasu rzeczywistego dobrą praktyką jest optymalizacja takich rzeczy”

    Hmm.. ale czy istnieje chociaż jeden śmiałek, który pisze systemy czasu rzeczywistego w C# [== pod Windowsa]?

  10. @Paweł:
    ToLower() utworzy nowego string’a. To kosztuje. Nie jestem przekonany czy to wyladuje w intern.

    Co do real-time: oczywiscie. Bardzo wiele takich systemow pisze się w Londynie – banki inwestycyjne itp.

  11. Co w przypadku gdy mamy listę stringów i chcemy znaleźć tylko takie, które zawierają określoną frazę? Oczywiście ignorując wielkość znaków. Wtedy funkcja Equals na nic się nie zda. Możemy skorzystać z funkcji Contains, lecz tutaj nie znajdziemy przeładowania, które umożliwi wykorzystanie StringComparison. Czy jesteśmy skazani na użycie funkcji ToLower? czy też jest jakaś alternatywa?

  12. Odpowiedź na moje pytanie powyżej można znaleźć tutaj: [http://stackoverflow.com/questions/444798/case-insensitive-containsstring]

  13. Czy da się dodać do tego możliwość porównania do jakiegoś znaku? Np do nawiasu “(” Chodzi mi o to, żeby można było porównując SAMOCHOD(skoda) i samochod(audi) otrzymać wynik, że są takie same.

Leave a Reply

Your email address will not be published.