Obsługa stringów w .NET 5.0 może popsuć istniejący kod
Nadchodząca wersja .NET przedstawi nowy sposób, w jaki działają niektóre funkcje. W tym kontekście jeden z developerów zauważył pewien problem — porównanie ciągów znaków może z tego względu popsuć istniejący kod na Windowsie. Problem stał się widoczny, kiedy Jimmy Bogard aktualizował swoją bibliotekę, tak aby obsługiwała .NET 5.0. Test się niestety nie powiódł. Bogard zwrócił na to uwagę na GitHubie, ale pewien inżynier z Microsoftu powiedział mu, że tak to już zostało w nowej wersji zaprojektowane. A zatem to nie bug — to nowa funkcja, tylko że działa inaczej, niż poprzednio.
Opis problemu
Napisany przez niego kod był w stanie odnaleźć jeden ciąg znaków wewnątrz innego, dzięki metodzie Contains
. Kiedy użył on jednak metody IndexOf
, aby odnaleźć lokalizację ciągu znaków, to zwróciła on -1
, co oznacza, że poszukiwany string nie został odnaleziony. Niemniej jednak wszystko działało tak, jak powinno na .NET Core 3.0 oraz 3.1 na Windowsie. Oto reprodukcja problemu dla .NET 5.0:
string text = "Line 1 \nLine 2";
string word = "\nLine";
bool containsWord = text.Contains(word); // zwraca true
int wordPosition = text.IndexOf(word); // zwraca -1
Powyższy bug ma miejsce tylko, gdy ciągi znaków zawierają specjalne znaki, takie jak znaki nowej linii, czy pewne znaki diakrytyczne. Wynika to trochę z tego, że metoda IndexOf
jest culture-sensitive, co oznacza, że niektóre znaki są ignorowane, a jeszcze inne mogą być uważane za ekwiwalenty. I w tym miejscu trzeba wspomnieć, że nowa wersja .NET odchodzi od standardu NLS i przechodzi na ICU — International Components for Unicode. Stąd IndexOf
i Contains
w .NET 5.0 nie są zawsze jednoznaczne.
Wykonywanie porównania typu culture-sensitive bez bezpośredniego określenia sposobu porównania, jest nieco śliskie.
Rozwiązanie Microsoftu
Microsoft rekomenduje developerom następujące rozwiązanie:
“Use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase for comparisons as your safe default for culture-agnostic string matching.”
StringComparison.OrdinalIgnoreCase
ma być bezpieczne i domyślne dla porównań ciągów znaków typu culture-sensitive.
Co więcej, inżynier Microsoftu twierdzi, że nie należy porównywać wyników otrzymanych po użyciu Contains
z wynikami IndexOf
bez parametrów StringComparison
. Niemniej jednak aplikacje robią rzeczy, których technicznie robić nie należy, a działają dobrze przez wiele, wiele lat.
Podczas gdy rozwiązanie Microsoftu wydaje się jak najbardziej OK, to jednak taki kod nadal może ulec uszkodzeniu, kiedy będziemy uaktualniać .NET Core 3.1 do .NET 5.0. Co gorsza, kod może się popsuć, gdy nie ma on pokrycia testów jednostkowych, które zawierają przykłady podzbioru porównania ciągu znaków, które zachowuje się w inny sposób.
Podsumowanie
Odkrycie powyższego może trochę martwić — w końcu Microsoft tego jasno nie zakomunikował. Czas pokaże, czy w .NET jest więcej tego typu niespodzianek, która pokryjomu psują kompatybilność wsteczną.
W tym konkretnym przypadku istnieje jednak droga naokoło. Programiści mogą ustawić w swoich projektach opcję, która pozwoli im używać NLS w .NET 5.0, co może rozwiązać problem na poziomie konfiguracji, a nie kodu.