Różnica między InvariantCulture a Ordinal string string

548

Porównując dwa ciągi w c # dla równości, jaka jest różnica między InvariantCulture a Ordinal porównaniem?

Kapil
źródło
Może siao2.com/2004/12/29/344136.aspx ? (googled)
Cheese Daneish
2
Dla tych String1.Equals(String2, StringComparison.Ordinal), którzy używają , lepiej użyć, String1 == String2które jest String1.Equals(String2)samo w sobie i jest to domyślnie porównanie z rozróżnianiem wielkości liter.
Ghasan,
3
@ Ghasan Nie jestem pewien, czy to czyni ==„lepszą”, ale jest to: a) krótsze, b) mniej wyraźne na temat tego, co dokładnie robi, oraz c) String1może być zerowe bez porównania NullReferenceException.
Eugene Beresovsky
3
@ Ghasan oficjalne najlepsze praktyki MSDN dotyczące używania ciągów na stronie .NET Framework ( msdn.microsoft.com/en-us/library/... ) zaleca użycie przeciążeń, które wyraźnie określają StringComparisontyp. W przypadku porównania ciągów oznacza to String.Equals.
Ohad Schneider,
3
@EugeneBeresovsky Aby uniknąć NullReferenceExceptionmożna po prostu użyć metody statycznej: String.Equals(string1, string2, StringComparison.Ordinal).
Ohad Schneider,

Odpowiedzi:

302

InvariantCulture

Wykorzystuje „standardowy” zestaw uporządkowania znaków (a, b, c, ... itd.). Jest to sprzeczne z niektórymi konkretnymi ustawieniami regionalnymi, które mogą sortować znaki w różnych porządkach („a-z-ostrym” może występować przed lub po „a”, w zależności od ustawień regionalnych itd.).

Porządkowy

Z drugiej strony, patrzy wyłącznie na wartości surowych bajtów, które reprezentują znak.


Świetna próbka znajduje się na stronie http://msdn.microsoft.com/en-us/library/e6883c06.aspx, która pokazuje wyniki różnych wartości StringComparison. Na samym końcu pokazuje (fragment):

StringComparison.InvariantCulture:
LATIN SMALL LETTER I (U+0069) is less than LATIN SMALL LETTER DOTLESS I (U+0131)
LATIN SMALL LETTER I (U+0069) is less than LATIN CAPITAL LETTER I (U+0049)
LATIN SMALL LETTER DOTLESS I (U+0131) is greater than LATIN CAPITAL LETTER I (U+0049)

StringComparison.Ordinal:
LATIN SMALL LETTER I (U+0069) is less than LATIN SMALL LETTER DOTLESS I (U+0131)
LATIN SMALL LETTER I (U+0069) is greater than LATIN CAPITAL LETTER I (U+0049)
LATIN SMALL LETTER DOTLESS I (U+0131) is greater than LATIN CAPITAL LETTER I (U+0049)

Widać, że tam, gdzie przynosi InvariantCulture (U + 0069, U + 0049, U + 00131), Zwykłe plony (U + 0049, U + 0069, U + 00131).

JaredReisinger
źródło
25
Porównywanie porządkowe dotyczy punktów kodowych , a nie bajtów.
Joey
143
Wydaje mi się, że to przydatna informacja, ale tak naprawdę nie odpowiada na pytanie. Czy przy określaniu równości dwóch ciągów jest jakiś powód, aby używać InvarintCulture zamiast Ordinal? Wygląda na to, że InvariantCulture byłoby używane do sortowania ciągów, a Ordinal powinien być używany do sprawdzania równości (nie obchodzi nas, że akcent-a pojawia się przed lub po, po prostu jest inny). Chociaż sam jestem trochę niepewny tego punktu.
MPavlak,
18
Zobacz msdn.microsoft.com/en-us/library/ms230117%28v=vs.90%29.aspx i zauważ, że zalecana jest normalizacja łańcuchów i porównanie porządkowe.
MPavlak,
23
Zamówienie jest znacznie szybsze
Darren
9
Opublikowano dobre wyniki testu wydajności Testy porównawcze łańcuchów znaków C #, które wskazują wydajność poszczególnych metod porównywania łańcuchów i ich czas.
Kumar C,
260

Ma to na przykład znaczenie - istnieje tzw. Ekspansja postaci

var s1 = "Strasse";
var s2 = "Straße";

s1.Equals(s2, StringComparison.Ordinal);           //false
s1.Equals(s2, StringComparison.InvariantCulture);  //true

Z InvariantCultureß znak zostaje rozszerzony do ss.

Ventsyslav Raikov
źródło
1
Czy to też różni się w jakiś sposób między Ordinali InvariantCulture? Właśnie o to chodzi w pierwotnym pytaniu.
Matthijs Wessels
3
Dla tych, którzy nie wiedzą ß, należy zauważyć, że ßprzynajmniej w języku niemieckim jest równy podwójnemu s, Źródło: en.wikipedia.org/wiki/%C3%9F
Peter
20
To nie do końca poprawne @Peter, nie możesz używać ßi sszamiennie w języku niemieckim (jestem native speakerem). Są przypadki, w których oba są legalne (ale często jeden jest przestarzały / niezalecany) i są przypadki, w których dozwolony jest tylko jeden formularz.
enzi
5
Ten prosty przykład wyraźnie pokazuje różnicę między 2 porównaniami. Myślę, że teraz to rozumiem.
BrianLegg
4
Musiałem spróbować: ideone.com/j8DvDo cool! Trochę lekcji w języku niemieckim. Zastanawiasz się, jaka jest teraz różnica między ß i ss ...
Zn
111

Wskazując na najlepsze praktyki korzystania z ciągów w .NET Framework :

  • Użyj StringComparison.Ordinallub StringComparison.OrdinalIgnoreCasedo porównań jako bezpiecznego domyślnego dopasowania łańcucha bez względu na kulturę.
  • Użyj porównań z StringComparison.Ordinallub StringComparison.OrdinalIgnoreCasedla lepszej wydajności.
  • Użyj operacji niejęzykowych StringComparison.Ordinallub StringComparison.OrdinalIgnoreCasewartości zamiast operacji na łańcuchach, w oparciu o to, CultureInfo.InvariantCulturekiedy porównanie nie ma znaczenia językowego (na przykład symboliczne).

I w końcu:

  • StringComparison.InvariantCultureW większości przypadków nie należy używać operacji na łańcuchach . Jednym z nielicznych wyjątków jest utrwalanie znaczących językowo, ale kulturowo agnostycznych danych.
Dariusz
źródło
56

Inną przydatną różnicą (w języku angielskim, gdzie akcenty są rzadkie) jest to, że porównanie InvariantCulture porównuje najpierw wszystkie ciągi znaków bez rozróżniania wielkości liter, a następnie, jeśli to konieczne (i wymagane), rozróżnia je po pierwszym, porównując tylko poszczególne litery. (Oczywiście można również przeprowadzić porównanie bez rozróżniania wielkości liter, które nie rozróżnia wielkości liter.) Poprawiono:Litery akcentowane są uważane za inny smak tych samych liter i ciąg jest porównywany najpierw ignorując akcenty, a następnie rozliczając je, jeśli wszystkie litery ogólne pasują (podobnie jak w przypadku różnych liter, z wyjątkiem tego, że ostatecznie nie jest ignorowane w porównaniu bez rozróżniania wielkości liter). Ta grupa akcentuje wersje skądinąd tego samego słowa blisko siebie, zamiast całkowicie rozdzielić przy pierwszej różnicy akcentu. Jest to kolejność sortowania, którą zwykle można znaleźć w słowniku: słowa pisane wielkimi literami pojawiają się tuż obok ich małych odpowiedników, a litery akcentowane znajdują się w pobliżu odpowiadającej im litery nieakcentowanej.

Porównanie porządkowe porównuje ściśle wartości liczbowe, zatrzymując się na pierwszej różnicy. To sortuje wielkie litery całkowicie oddzielone od małych liter (i litery akcentowane prawdopodobnie oddzielne od tych), więc wielkie litery nie posortowałyby nigdzie w pobliżu ich małych odpowiedników.

InvariantCulture również uważa, że ​​wielkie litery są większe niż małe litery, podczas gdy Ordinal uważa, że ​​wielkie litery są mniejsze niż małe litery (pozostałość ASCII z dawnych czasów, zanim komputer miał małe litery, wielkie litery były przydzielane jako pierwsze, a zatem miały niższe wartości niż małe litery dodane później).

Na przykład według Ordinal: "0" < "9" < "A" < "Ab" < "Z" < "a" < "aB" < "ab" < "z" < "Á" < "Áb" < "á" < "áb"

I przez InvariantCulture: "0" < "9" < "a" < "A" < "á" < "Á" < "ab" < "aB" < "Ab" < "áb" < "Áb" < "z" < "Z"

Rob Parker
źródło
Spojrzałem na to jeszcze raz i zauważyłem niespójność między przykładem InvariantCulture a moim wyjaśnieniem dotyczącym obsługi znaków akcentowanych. Przykład wydaje się poprawny, więc poprawiłem wyjaśnienie, aby było spójne. Porównanie InvariantCulture nie kończy się na pierwszym różniącym się akcentie i wydaje się, że bierze pod uwagę różnicę akcentu na tej samej literze, jeśli pozostałe ciągi pasują oprócz akcentów i wielkości liter. Różnica akcentu jest następnie rozważana przed różnicą wcześniejszych przypadków, więc „Aaba” <„aába”.
Rob Parker,
31

Chociaż pytanie dotyczy równości , dla szybkiego wizualnego odniesienia, tutaj kolejność niektórych ciągów posortowanych za pomocą kilku kultur ilustrujących niektóre z tych dziwactw.

Ordinal          0 9 A Ab a aB aa ab ss Ä Äb ß ä äb      
IgnoreCase       0 9 a A aa ab Ab aB ss ä Ä äb Äb ß      
--------------------------------------------------------------------
InvariantCulture 0 9 a A  ä Ä aa ab aB Ab äb Äb ss ß     
IgnoreCase       0 9 A a  Ä ä aa Ab aB ab Äb äb ß ss     
--------------------------------------------------------------------
da-DK            0 9 a A  ab aB Ab ss ß ä Ä äb Äb aa     
IgnoreCase       0 9 A a  Ab aB ab ß ss Ä ä Äb äb aa     
--------------------------------------------------------------------
de-DE            0 9 a A  ä Ä aa ab aB Ab äb Äb ß ss     
IgnoreCase       0 9 A a  Ä ä aa Ab aB ab Äb äb ss ß     
--------------------------------------------------------------------
en-US            0 9 a A  ä Ä aa ab aB Ab äb Äb ß ss     
IgnoreCase       0 9 A a  Ä ä aa Ab aB ab Äb äb ss ß     
--------------------------------------------------------------------
ja-JP            0 9 a A  ä Ä aa ab aB Ab äb Äb ß ss     
IgnoreCase       0 9 A a  Ä ä aa Ab aB ab Äb äb ss ß     

Obserwacje:

  • de-DE, ja-JPi en-USposortuj w ten sam sposób
  • Invarianttylko sortuje ssi ßinaczej niż powyższe trzy kultury
  • da-DK sortuje się zupełnie inaczej
  • te IgnoreCasesprawy flag dla wszystkich badanych kultur

Kod użyty do wygenerowania powyższej tabeli:

var l = new List<string>
    { "0", "9", "A", "Ab", "a", "aB", "aa", "ab", "ss", "ß",
      "Ä", "Äb", "ä", "äb", "あ", "ぁ", "ア", "ァ", "A", "亜" };

foreach (var comparer in new[]
{
    StringComparer.Ordinal,
    StringComparer.OrdinalIgnoreCase,
    StringComparer.InvariantCulture,
    StringComparer.InvariantCultureIgnoreCase,
    StringComparer.Create(new CultureInfo("da-DK"), false),
    StringComparer.Create(new CultureInfo("da-DK"), true),
    StringComparer.Create(new CultureInfo("de-DE"), false),
    StringComparer.Create(new CultureInfo("de-DE"), true),
    StringComparer.Create(new CultureInfo("en-US"), false),
    StringComparer.Create(new CultureInfo("en-US"), true),
    StringComparer.Create(new CultureInfo("ja-JP"), false),
    StringComparer.Create(new CultureInfo("ja-JP"), true),
})
{
    l.Sort(comparer);
    Console.WriteLine(string.Join(" ", l));
}
Eugene Beresovsky
źródło
1
Hmmm - OK, miło, że przeprowadziłeś te badania i opublikowałeś swoje odkrycia, chociaż nie jestem do końca pewien, o co ci chodzi. W każdym razie duński może nie być jedną z „najważniejszych kultur” (chociaż 5 milionów Duńczyków tak naprawdę lubi swoją kulturę), ale jeśli wrzucisz „aa” jako dodatkowy ciąg testowy, a „da-DK” jako dodatkowa kultura testowa, zobaczysz kilka interesujących wyników.
RenniePet
1
@RenniePet Dzięki za to. Dodałem duński, ponieważ sortuje się on zupełnie inaczej niż 3 inne zastosowane kultury. (Ponieważ emotikony wskazujące na ironię nie wydają się być tak dobrze rozumiane w sieci do czytania w języku angielskim, jak się spodziewałbym, usunąłem komentarz dotyczący „najważniejszych kultur”. W końcu w BCL nie ma takiego, CultureComparerktórego moglibyśmy użyć do weryfikacji. W przypadku tej tabeli Danishkultura (informacje) okazała się bardzo ważna.)
Eugene Beresovsky
1
Dzięki. Zdałem sobie sprawę, że wasz komentarz do „najważniejszych kultur” miał być zrobiony z ziarenkiem soli - po prostu jestem za stary, by używać emotikonów. Wydaje mi się, że SMS-y stały się tak powszechne, że używanie emotikonów przypomina trochę wyjaśnianie dowcipów po ich przekazaniu, niezależnie od tego, czy ktoś się śmieje. Nawiasem mówiąc, inne kultury skandynawskie (fińska, norweska i szwedzka) są takie same jak duńskie, z wyjątkiem bardzo specjalnego traktowania „aa” - co dowodzi, że duński jest oczywiście kulturą wyższą.
RenniePet
1
Co do wartości, duński sortuje ä i aa inaczej ze względu na położenie specjalnych liter æ (ae), ø (oe, ö) i å (aa, ä) na końcu alfabetu w kolejności pisemnej.
Alrekr
5

Oto przykład, w którym porównanie równości łańcucha przy użyciu InvariantCultureIgnoreCase i OrdinalIgnoreCase nie da takich samych wyników:

string str = "\xC4"; //A with umlaut, Ä
string A = str.Normalize(NormalizationForm.FormC);
//Length is 1, this will contain the single A with umlaut character (Ä)
string B = str.Normalize(NormalizationForm.FormD);
//Length is 2, this will contain an uppercase A followed by an umlaut combining character
bool equals1 = A.Equals(B, StringComparison.OrdinalIgnoreCase);
bool equals2 = A.Equals(B, StringComparison.InvariantCultureIgnoreCase);

Jeśli to uruchomisz, equals1 będzie fałszywe, a equals2 będzie prawdziwe.

Dwedit
źródło
Aby dodać kolejny podobny przykład, ale z literałami łańcuchowymi, jeśli a="\x00e9"(e ostre) i b="\x0065\x0301"(e połączone z ostrym akcentem), StringComparer.Ordinal.Equals(a, b)zwróci false, a StringComparer.InvariantCulture.Equals(a, b)zwróci true.
George Helyar,
2

Nie trzeba używać fantazyjnych znaków Unicode, aby pokazać różnicę. Oto jeden prosty przykład, który odkryłem dzisiaj, który jest zaskakujący, składający się wyłącznie z znaków ASCII.

Zgodnie z tabelą ASCII 0(0x48) jest mniejsze niż _(0x95) w porównaniu ze zwykłym. InvariantCulture powiedziałoby coś przeciwnego (kod PowerShell poniżej):

PS> [System.StringComparer]::Ordinal.Compare("_", "0")
47
PS> [System.StringComparer]::InvariantCulture.Compare("_", "0")
-1
KFL
źródło
-7

Zawsze staraj się używać InvariantCulture w tych metodach łańcuchowych, które akceptują to jako przeciążenie. Korzystając z InvariantCulture jesteś po bezpiecznej stronie. Wielu programistów .NET może nie korzystać z tej funkcji, ale jeśli twoje oprogramowanie będzie używane przez różne kultury, InvariantCulture jest niezwykle przydatną funkcją.

Jerzy
źródło
3
Jeśli twoje oprogramowanie nie będzie używane przez różne kultury, jest jednak znacznie wolniejsze niż Ordinal.
Kyle
4
Zastanawiałem się nad oddaniem głosu, ponieważ z pewnością nie przemyślałeś swojej przypadkowej odpowiedzi. Chociaż w nim jest ziarno prawdy. JEŻELI twoja aplikacja jest szeroko rozpowszechniona w wielu kulturach ... To z pewnością nie uzasadnia twoich początkowych słów: „Zawsze staraj się używać InvariantCulture”, prawda? Dziwi mnie, że nie wróciłeś przez lata, aby edytować to szaleństwo po otrzymaniu opinii negatywnej i być może większego doświadczenia.
Suamere