Ajánlott eljárások sztringek összehasonlítására a .NET-ben

A .NET széles körű támogatást nyújt a honosított és globalizált alkalmazások fejlesztéséhez, és megkönnyíti a jelenlegi vagy egy adott kultúra konvenciók alkalmazását olyan gyakori műveletek végrehajtásakor, mint a sztringek rendezése és megjelenítése. A karakterláncok rendezése és összehasonlítása azonban nem mindig kulturálisan érzékeny művelet. Az alkalmazások által belsőleg használt sztringeket például általában minden kultúrában azonos módon kell kezelni. Ha a kulturálisan független sztringadatok, például XML-címkék, HTML-címkék, felhasználónevek, fájlelérési utak és a rendszerobjektumok nevei úgy vannak értelmezve, mintha kultúraérzékenyek lennének, az alkalmazáskódok apró hibákra, gyenge teljesítményre és bizonyos esetekben biztonsági problémákra lehetnek kitéve.

Ez a cikk a .NET-ben a sztringek rendezési, összehasonlítási és burkolatkezelési módszereit vizsgálja, javaslatot tesz a megfelelő sztringkezelési módszer kiválasztására, és további információkat nyújt a sztringkezelési módszerekről.

Javaslatok sztringhasználatra

Ha .NET keretrendszerrel fejleszt, kövesse ezeket a javaslatokat a karakterláncok összehasonlításakor.

Jótanács

A karakterlánccal kapcsolatos különböző metódusok összehasonlítást végeznek. Ilyenek például a következők: String.Equals, String.Compare, String.IndexOfés String.StartsWith.

Sztringek összehasonlítása esetén kerülje az alábbi eljárásokat:

  • Ne használjon olyan túlterheléseket, amelyek nem explicit módon vagy implicit módon határozzák meg a sztring-összehasonlítási szabályokat a sztringműveletek esetében.
  • Legtöbb esetben ne használjon sztringműveleteket StringComparison.InvariantCulture. A néhány kivétel egyike, ha nyelvileg jelentőséggel bíró, de kulturálisan agnosztikus adatokat őriz meg.
  • Ne használja a String.Compare vagy CompareTo metódus túlterhelését, és ne tesztelje a nulla visszatérési értéket annak megállapításához, hogy két karakterlánc egyenlő-e.

Jótanács

A CA1307, CA1309 és CA1310 kódelemzési szabályok segítenek azonosítani azokat a híváshelyeket, ahol a nyelvi összehasonlítót véletlenül használják. Ha engedélyezni szeretné ezeket, és a szabálysértéseket építési hibákként szeretné megjeleníteni, állítsa be a következő tulajdonságokat a projektfájlban:

<PropertyGroup>
  <AnalysisMode>All</AnalysisMode>
  <WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>

Adja meg kifejezetten a szöveg-összehasonlításokat.

A .NET-ben a sztringmanipulálási módszerek többsége túlterhelt. Általában egy vagy több túlterhelés fogadja el az alapértelmezett beállításokat, míg mások nem fogadnak el alapértelmezett értékeket, és ehelyett meghatározzák a sztringek összehasonlításának vagy módosításának pontos módját. Az alapértelmezett értékekre nem támaszkodó metódusok többsége tartalmaz egy típusparamétert StringComparison, amely egy számbavétel, amely explicit módon határozza meg a sztringek összehasonlításának szabályait kultúra és eset szerint. Az alábbi táblázat az enumerálási StringComparison tagokat ismerteti.

StringComparison tag Leírás
CurrentCulture Kis- és nagybetűket megkülönböztető összehasonlítást végez az aktuális kultúra szerint.
CurrentCultureIgnoreCase Kis- és nagybetűkre nem érzékeny összehasonlítást hajt végre az aktuális kultúra alapján.
InvariantCulture Az invariáns kultúra használatával kis- és nagybetűket megkülönböztető összehasonlítást végez.
InvariantCultureIgnoreCase Kis- és nagybetűket nem érzékelyítő összehasonlítást végez az invariáns kultúra használatával.
Ordinal Ordinális összehasonlítást végez.
OrdinalIgnoreCase Kis- és nagybetűkre nem érzékeny ordinális összehasonlítás.

A IndexOf metódus például, amely visszaadja egy karakternek vagy sztringnek megfelelő részsztring indexét egy String objektumban, kilenc túlterheléssel rendelkezik.

Javasoljuk, hogy az alábbi okokból válasszon túlterhelést, amely nem használ alapértelmezett értékeket:

  • Néhány túlterhelés (amelyek alapértelmezett paraméterekkel egy adott karaktert, például a Char-t keresnek a sztringpéldányban) sorszám-összehasonlítást végez, míg mások (amelyek sztringet keresnek a sztringpéldányban) kulturálisan érzékenyek. Nehéz megjegyezni, hogy melyik metódus melyik alapértelmezett értéket használja, és könnyen összekeverheti a túlterheléseket.

  • A metódushívások alapértelmezett értékeire támaszkodó kód szándéka nem egyértelmű. A következő példában, amely az alapértelmezett értékekre támaszkodik, nehéz megállapítani, hogy a fejlesztő valójában két sztring ordinális vagy nyelvi összehasonlítását szánta-e, vagy a url.Scheme és a "https" közötti kis- és nagybetűk közötti különbség okozhatja-e az egyenlőségi teszt visszatérését false.

    Uri url = new("https://learn.microsoft.com/");
    
    // Incorrect
    if (string.Equals(url.Scheme, "https"))
    {
        // ...Code to handle HTTPS protocol.
    }
    
    Dim url As New Uri("https://learn.microsoft.com/")
    
    ' Incorrect
    If String.Equals(url.Scheme, "https") Then
        ' ...Code to handle HTTPS protocol.
    End If
    

Általában azt javasoljuk, hogy olyan metódust hívjon meg, amely nem támaszkodik az alapértelmezett értékekre, mert egyértelművé teszi a kód szándékát. Ez pedig olvashatóbbá és könnyebben kezelhetővé teszi a kódot. Az alábbi példa az előző példával kapcsolatban felmerült kérdésekre ad választ. Egyértelművé teszi, hogy a rendszer a sorszám-összehasonlítást használja, és hogy a különbségeket figyelmen kívül hagyja.

Uri url = new("https://learn.microsoft.com/");

// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
    // ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")

' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
    ' ...Code to handle HTTPS protocol.
End If

A sztringek összehasonlításának részletei

A sztringek összehasonlítása számos sztringgel kapcsolatos művelet középpontjában áll, különösen az egyenlőség rendezése és tesztelése. Sztringek meghatározott sorrendben rendezve: Ha a "my" a "sztring" előtt jelenik meg a sztringek rendezett listájában, a "my" értéknek kisebbnek vagy egyenlőnek kell lennie a "sztring" értéknél. Emellett az összehasonlítás implicit módon definiálja az egyenlőséget. Az összehasonlító művelet nullát ad vissza az általa egyenlőnek ítélt sztringekhez. Jó megállapítás, hogy egyik sztring sem kisebb a másiknál. A sztringeket tartalmazó legérthetőbb műveletek közé tartozik az egyik vagy mindkét eljárás: egy másik sztringgel való összehasonlítás és egy jól definiált rendezési művelet végrehajtása.

Megjegyzés

Letöltheti a Rendezési súlytáblákat, a Windows operációs rendszerek rendezési és összehasonlítási műveleteihez használt karaktersúlyokra vonatkozó információkat tartalmazó szövegfájlokat, valamint a Linux és macOS rendszerekhez készült rendezési súlytáblázat legújabb verzióját, az Alapértelmezett Unicode rendezési elem táblát. A linuxos és macOS rendszerű rendezési súlytábla konkrét verziója a rendszeren telepített Unicode-kódtárak nemzetközi összetevőinek verziójától függ. Az ICU-verziókról és az általuk implementálható Unicode-verziókról további információt az ICU letöltése című témakörben talál.

Az egyenlőségi vagy rendezési sorrend két sztringjének kiértékelése azonban nem eredményez egyetlen, helyes eredményt; az eredmény a sztringek összehasonlításához használt feltételektől függ. Az aktuális kultúra vagy az invariáns kultúra (az angol nyelvre épülő területi-agnosztikus kultúra) keretezési és rendezési konvencióin alapuló sztring-összehasonlítások eltérő eredményeket eredményezhetnek.

Emellett a .NET különböző verzióit használó, illetve a .NET-et különböző operációs rendszereken vagy operációsrendszer-verziókon használó sztring-összehasonlítások eltérő eredményeket adhatnak vissza. A .NET az International Components for Unicode (ICU) kódtárat használja a nyelvi sztringek összehasonlításához az összes támogatott platformon. További információ: Strings and the Unicode Standard and .NET globalization and ICU.

Az aktuális kultúrát alkalmazó sztring-összehasonlítások

Az egyik kritérium a sztringek összehasonlítása során az aktuális kultúra konvencióit használja. Az aktuális kultúrán alapuló összehasonlítások a szál aktuális kultúráját vagy területi beállítását használják. Ha a kultúrát nem a felhasználó állítja be, alapértelmezés szerint az operációs rendszer beállítását veszi át. Mindig olyan összehasonlításokat kell használnia, amelyek a jelenlegi kultúrán alapulnak, amikor az adatok nyelvi szempontból relevánsak, és amikor azok a kultúra szempontjából érzékeny felhasználói interakciókat tükrözik.

A .NET-ben azonban az összehasonlítás és a burkolat viselkedése megváltozik, amikor a kultúra megváltozik. Ez akkor fordul elő, ha egy alkalmazás olyan számítógépen fut, amely más kultúrával rendelkezik, mint az a számítógép, amelyen az alkalmazást fejlesztették, vagy amikor a végrehajtó szál megváltoztatja a kultúráját. Ez a viselkedés szándékos, de sok fejlesztő számára nem nyilvánvaló. Az alábbi példa az amerikai angol ("en-US") és a svéd ("sv-SE") kultúrák közötti rendezési sorrend különbségeit mutatja be. Vegye figyelembe, hogy az "ångström", a "Windows" és a "Visual Studio" szavak a rendezett sztringtömbök különböző pozícióiban jelennek meg.

using System.Globalization;

// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
                    "Windows", "Visual Studio" };

// Current culture
Array.Sort(values);
DisplayArray(values);

// Change culture to Swedish (Sweden)
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);

// Restore the original culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);

static void DisplayArray(string[] values)
{
    Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
    
    foreach (string value in values)
        Console.WriteLine($"   {value}");

    Console.WriteLine();
}

// The example displays the following output:
//     Sorting using the en-US culture:
//        able
//        Æble
//        ångström
//        apple
//        Visual Studio
//        Windows
//
//     Sorting using the sv-SE culture:
//        able
//        apple
//        Visual Studio
//        Windows
//        ångström
//        Æble
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        ' Words to sort
        Dim values As String() = {"able", "ångström", "apple", "Æble",
                                  "Windows", "Visual Studio"}

        ' Current culture
        Array.Sort(values)
        DisplayArray(values)

        ' Change culture to Swedish (Sweden)
        Dim originalCulture As String = CultureInfo.CurrentCulture.Name
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values)
        DisplayArray(values)

        ' Restore the original culture
        Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
    End Sub

    Sub DisplayArray(values As String())
        Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")

        For Each value As String In values
            Console.WriteLine($"   {value}")
        Next

        Console.WriteLine()
    End Sub
End Module

' The example displays the following output:
'     Sorting using the en-US culture:
'        able
'        Æble
'        ångström
'        apple
'        Visual Studio
'        Windows
'
'     Sorting using the sv-SE culture:
'        able
'        apple
'        Visual Studio
'        Windows
'        ångström
'        Æble

A szál aktuális kultúráját használó kis- és nagybetűérzéketlen összehasonlítások megegyeznek a kultúraérzékeny összehasonlításokkal, azzal a különbséggel, hogy figyelmen kívül hagyják a kis- és nagybetűket az aktuális kultúra szerint. Lehet, hogy ez a viselkedés a rendezési sorrendekben is megnyilvánul.

Az alábbi módszerek alapértelmezetten az aktuális kulturális szemantikát használó összehasonlításokat használják:

Mindenesetre azt javasoljuk, hogy olyan túlterhelést hívjon meg, amelynek paramétere StringComparison egyértelművé teszi a metódushívás szándékát.

Finom és nem annyira finom hibák akkor jelentkezhetnek, ha a nem nyelvi sztringadatokat nyelvileg értelmezik, vagy ha egy adott kultúrából származó sztringadatokat egy másik kultúra konvenciói alapján értelmeznek. A Turkish-I probléma a kanonikus példa.

Szinte minden latin ábécé esetében, beleértve az amerikai angolt is, az "i" (\u0069) karakter az "I" (\u0049) karakter kisbetűs változata. Ez a casing szabály gyorsan az alapértelmezetté válik egy ilyen kultúrában programozó személy számára. A török ("tr-TR") ábécé azonban tartalmaz egy "I with a dot" (İ) karaktert (\u0130), amely az "i" nagybetűs változata. A törökben szerepel egy pont nélküli kis "i" karakter, "ı" (\u0131), amely nagybetűvé "I" változik. Ez a viselkedés az azerbajdzsáni ("az") kultúrában is előfordul.

Ezért az "i" vagy az "I" nagybetűsítésével kapcsolatos feltételezések nem érvényesek minden kultúrában. Ha a sztring-összehasonlítási rutinokhoz az alapértelmezett túlterheléseket használja, akkor azok a kultúrák közötti eltérésnek lesznek kitéve. Ha az összehasonlítandó adatok nyelven kívüliek, az alapértelmezett beállítások nem kívánt eredményekhez vezethetnek, például ahogyan az alábbi kísérlet bemutatja a "bill" és "BILL" karakterláncok kis- és nagybetűkre nem érzékeny összehasonlítását.

using System.Globalization;

string name = "Bill";

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");

//' The example displays the following output:
//'
//'     Culture = English (United States)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? True
//'     
//'     Culture = Turkish (Türkiye)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        Dim name As String = "Bill"

        Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
    End Sub

End Module

' The example displays the following output:
'
'     Culture = English (United States)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? True
'     
'     Culture = Turkish (Türkiye)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? False

Ez az összehasonlítás jelentős problémákat okozhat, ha a kultúrát véletlenül használják biztonsági szempontból érzékeny beállításokban, ahogyan az alábbi példában is látható. Egy metódushívás, például IsFileURI("file:") akkor ad true vissza, ha a jelenlegi kultúra amerikai angol, de false ha a jelenlegi kultúra török. Így a török rendszereken valaki megkerülheti a "FILE:" kezdetű, kis- és nagybetűkkel nem érzéketlen URI-khoz való hozzáférést megakadályozó biztonsági intézkedéseket.

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", true, null);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", True, Nothing)
End Function

Ebben az esetben, mivel a "file:" nem nyelvi, kulturális érzéketlen azonosítóként van értelmezve, a kódot inkább az alábbi példában látható módon kell megírni:

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

Sorszámozott karakterlánc műveletek

A metódushívásban megadott StringComparison.Ordinal vagy StringComparison.OrdinalIgnoreCase érték nem nyelvi összehasonlítást jelent, amely figyelmen kívül hagyja a természetes nyelvek jellemzőit. Az ezekkel az StringComparison értékekkel meghívott metódusok a sztringműveleteket egyszerű bájt-összehasonlításokra alapozza a kultúra által paraméterezett casing vagy ekvivalenciatáblák helyett. Ez a megközelítés a legtöbb esetben a sztringek tervezett értelmezéséhez illeszkedik, miközben a kód gyorsabb és megbízhatóbb.

Az ordinális összehasonlítások olyan karakterlánc-összehasonlítások, amelyek során az egyes karakterláncok minden bájtját nyelvi értelmezés nélkül hasonlítják össze; például a "windows" nem egyezik a "Windows" kifejezéssel. Ez lényegében a C futtatókörnyezet strcmp függvény hívása. Ezt az összehasonlítást akkor használja, ha a környezet azt diktálja, hogy a karakterláncok pontosan egyezzenek meg, vagy konzervatív egyeztetési eljárást követeljen meg. Emellett a sorszámos összehasonlítás a leggyorsabb összehasonlítási művelet, mivel nem alkalmaz nyelvi szabályokat az eredmény meghatározásakor.

A OrdinalIgnoreCase összehasonlító továbbra is karakterenkénti alapon működik, de kiküszöböli a kis- és nagybetűk közötti különbségeket a művelet végrehajtása során. Összehasonlításkor a karakterpárok OrdinalIgnoreCase és 'd' az 'D' tekintendők, mint ahogy a karakterpárok és 'á' esetében is. A ékezet nélküli karakter 'a' azonban nem egyenlő az ékezetes karakterrel 'á'.

Erre néhány példát az alábbi táblázat tartalmaz:

1. sztring Karakterlánc 2 Ordinal Összehasonlítás OrdinalIgnoreCase Összehasonlítás
"dog" "dog" egyenlő egyenlő
"dog" "Dog" nem egyenlő egyenlő
"resume" "résumé" nem egyenlő nem egyenlő

A Unicode lehetővé teszi, hogy a sztringek több különböző memóriabeli ábrázolásokkal rendelkezzenek. Az e-akut (é) például kétféleképpen ábrázolható:

  • Egyetlen literális 'é' karakter (más néven '\u00E9').
  • Egy literális nem konstans 'e' karakter, amelyet egy ékezetmódosító karakter '\u0301'kombinálása követ.

Ez azt jelenti, hogy a következő négy sztring mind úgy jelenik meg, mint "résumé"annak ellenére, hogy azok alkotórészei eltérőek. A sztringek a literális 'é' karakterek vagy a literális ékezet nélküli 'e' karakterek kombinációját alkalmazzák az ékezetmódosító '\u0301' segítségével.

  • "r\u00E9sum\u00E9"
  • "r\u00E9sume\u0301"
  • "re\u0301sum\u00E9"
  • "re\u0301sume\u0301"

Egy sorszám-összehasonlítóval egyik karakterlánc sem egyezik a másikkal. Ennek az az oka, hogy mindegyik különböző mögöttes karaktersorozatokat tartalmaz, annak ellenére, hogy a képernyőre renderelve mindegyik ugyanúgy néz ki.

A string.IndexOf(..., StringComparison.Ordinal) művelet végrehajtásakor a futtatókörnyezet pontos részsztring egyezést keres. Az eredmények a következők.

Console.WriteLine("resume".IndexOf('e', StringComparison.Ordinal)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("resume".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Sub IndexOfExample()
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.Ordinal)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
End Sub

A szokásos keresési és összehasonlító rutinokat soha nem befolyásolja az aktuális szál kulturális beállítása.

A .NET-sztringek beágyazott null karaktereket (és más nem nyomtatható karaktereket) tartalmazhatnak. A sorszám- és kultúraérzékeny összehasonlítás (beleértve az invariáns kultúrát használó összehasonlításokat is) egyik legtisztább különbsége a beágyazott null karakterek sztringben való kezelésére vonatkozik. Ezeket a karaktereket a rendszer figyelmen kívül hagyja, amikor a String.Compare kulturális szempontból érzékeny összehasonlításokat és String.Equals metódusokat használja (beleértve az invariáns kultúrát használó összehasonlításokat is). Ennek eredményeképpen a beágyazott null karaktereket tartalmazó sztringek egyenlőnek tekinthetők azokkal a sztringekkel, amelyek nem. Beágyazott nem nyomtatható karakterek kihagyhatók a karakterlánc-összehasonlító módszerek, például String.StartsWith esetében.

Fontos

Bár a sztring-összehasonlító metódusok figyelmen kívül hagyják a beágyazott null karaktereket, a sztringkeresési módszerek, mint például a String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf és String.StartsWith nem.

Az alábbi példa az "Aa" sztring kultúraérzékeny összehasonlítását hajtja végre egy hasonló sztringgel, amely több beágyazott null karaktert tartalmaz az "A" és az "a" között, és megmutatja, hogy a két sztring hogyan tekinthető egyenlőnek:

string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";

Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");

string ShowBytes(string value)
{
   string hexString = string.Empty;
   for (int index = 0; index < value.Length; index++)
   {
      string result = Convert.ToInt32(value[index]).ToString("X4");
      result = string.Concat(" ", result.Substring(0,2), " ", result.Substring(2, 2));
      hexString += result;
   }
   return hexString.Trim();
}

// The example displays the following output:
//     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
//        With String.Compare:
//           Current Culture: 0
//           Invariant Culture: 0
//        With String.Equals:
//           Current Culture: True
//           Invariant Culture: True

Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
    '        With String.Compare:
    '           Current Culture: 0
    '           Invariant Culture: 0
    '        With String.Equals:
    '           Current Culture: True
    '           Invariant Culture: True
End Module

A sztringek azonban nem tekinthetők egyenlőnek ordinalis összehasonlítás használatakor, ahogy az alábbi példa mutatja.

string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");

string ShowBytes(string str)
{
    string hexString = string.Empty;
    for (int ctr = 0; ctr < str.Length; ctr++)
    {
        string result = Convert.ToInt32(str[ctr]).ToString("X4");
        result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2);
        hexString += result;
    }
    return hexString.Trim();
}

// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False
Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
    '       With String.Compare:
    '          Ordinal: 97
    '       With String.Equals:
    '          Ordinal: False
End Module

A kis- és nagybetűket figyelmen kívül hagyó összehasonlítás a következő legkonzervatívabb megközelítés. Ezek az összehasonlítások figyelmen kívül hagyják a legtöbb burkolatot; például a "windows" egyezik a "Windows" értékkel. Az ASCII-karakterek kezelésekor ez a szabályzat egyenértékű StringComparison.Ordinalazzal a különbséggel, hogy figyelmen kívül hagyja a szokásos ASCII-burkolatot. Ezért az [A, Z] (\u0041-\u005A) bármely karaktere megegyezik az [a,z] (\u0061-\007A) megfelelő karakterével. Az ASCII-tartományon kívüli burkolatok az invariáns kultúra tábláit használják. Ezért a következő összehasonlítás:

string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

egyenértékű az összehasonlítással (de gyorsabb), mint:

string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)

Ezek az összehasonlítások még mindig nagyon gyorsak.

Mindkettőt StringComparison.Ordinal , és StringComparison.OrdinalIgnoreCase használja közvetlenül a bináris értékeket, és a legjobban alkalmas az egyeztetésre. Ha nem biztos az összehasonlítási beállításokban, használja a két érték egyikét. Mivel azonban bájtonkénti összehasonlítást végeznek, nem nyelvi rendezési sorrend (például angol szótár) szerint rendeznek, hanem bináris rendezési sorrend szerint. Az eredmények a legtöbb környezetben furcsának tűnhetnek, ha a felhasználók számára megjelennek.

Az ordinális szemantika az alapértelmezett viselkedés az olyan túlterhelések esetében, amelyek nem tartalmaznak String.Equals argumentumot (ideértve az egyenlőségi operátort is). Mindenesetre azt javasoljuk, hogy egy StringComparison paraméterrel rendelkező túlterhelést hívjon meg.

Nyelvi karakterláncok összehasonlítása

A nyelvi keresési és összehasonlító rutinok rendezési elemekre bontanak egy sztringet, és ezeken az elemeken végeznek keresést vagy összehasonlításokat. Nem feltétlenül van 1:1 leképezés egy karakterlánc karakterei és az azt alkotó összehasonlító elemek között. Egy 2. hosszúságú sztring például csak egyetlen rendezési elemből állhat. Ha két sztringet hasonlít össze nyelvtudatos módon, az összehasonlító ellenőrzi, hogy a két sztring rendezési elemei azonos szemantikai jelentéssel rendelkeznek-e, még akkor is, ha a sztring literális karakterei eltérőek.

Vegye figyelembe az előző szakaszban leírt sztringet "résumé" és annak négy különböző ábrázolását. Az alábbi táblázat az egyes ábrázolásokat rendezési elemekre bontva mutatja be.

Lánc Rendezési elemekként
"r\u00E9sum\u00E9" "r" + "\u00E9" + "s" + "u" + "m" + "\u00E9"
"r\u00E9sume\u0301" "r" + "\u00E9" + "s" + "u" + "m" + "e\u0301"
"re\u0301sum\u00E9" "r" + "e\u0301" + "s" + "u" + "m" + "\u00E9"
"re\u0301sume\u0301" "r" + "e\u0301" + "s" + "u" + "m" + "e\u0301"

A rendezési elem lazán megfelel annak, amit az olvasók egyetlen karakternek vagy karakterhalmaznak gondolnának. Fogalmilag hasonló a gráffürthöz , de egy valamivel nagyobb esernyőt foglal magában.

Nyelvi összehasonlító esetén nincs szükség pontos egyezésekre. A rendezési elemeket inkább szemantikai jelentésük alapján hasonlítják össze. A nyelvi összehasonlítók például egyenlőként kezelik a részszűkítéseket "\u00E9""e\u0301" , mivel mindkettő szemantikailag "egy kisbetűt és egy akut hangsúlymódosítót" jelent. Ez lehetővé teszi, hogy a IndexOf metódus egy nagyobb sztringen belül egyezzen a szemantikailag egyenértékű részsztringgel "e\u0301""\u00E9", ahogy az az alábbi kódmintában látható.

Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // "résumé": prints '-1' (not found)
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("\u00E9")); // "résumé": prints '1'
Console.WriteLine("\u00E9".IndexOf("e\u0301")); // prints '0'
Sub IndexOfStringExample()
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e")) ' "résumé": prints '-1' (not found)
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf(ChrW(&HE9).ToString())) ' "résumé": prints '1'
    Console.WriteLine(ChrW(&HE9).ToString().IndexOf("e" & ChrW(&H301))) ' prints '0'
End Sub

Ennek következtében két különböző hosszúságú karakterlánc egyenlő lehet, ha nyelvi összehasonlítást használ. A hívónak ügyelnie kell arra, hogy az ilyen helyzetekben ne alkalmazzanak különleges logikát, amely a karakterlánc hosszával kapcsolatos.

A kultúratudatos keresési és összehasonlító rutinok a nyelvi keresési és összehasonlító rutinok speciális formái. A kultúratudatos összehasonlítók esetében a rendezési elem fogalma ki van terjesztve a megadott kultúrára jellemző információkra.

A magyar ábécében például, amikor a két karakter <dz> visszafelé jelenik meg, a d vagy <z> betűtől <> eltérő egyedi betűnek számít. Ez azt jelenti, hogy amikor <karakterláncban dz> látható, a magyar kultúratudatos összehasonlító egyetlen rendezési elemként kezeli.

Lánc Rendezési elemekként Megjegyzések
"endz" "e" + "n" + "d" + "z" (szabványos nyelvi összehasonlító használatával)
"endz" "e" + "n" + "dz" (magyar kultúratudatos összehasonlító használata)

Magyar kultúratudatos összehasonlító használata esetén a sztring "endz"nem végződik az aláhúzással megadott részszöveggel "z", mivel <a "dz"> és <a "z"> különböző szemantikai jelentésű rendezési elemeket képeznek.

// Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU");
Console.WriteLine("endz".EndsWith("z")); // Prints 'False'

// Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("endz".EndsWith("z")); // Prints 'True'
' Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU")
Console.WriteLine("endz".EndsWith("z")) ' Prints 'False'

' Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture
Console.WriteLine("endz".EndsWith("z")) ' Prints 'True'

Megjegyzés

  • Viselkedés: A nyelvi és kultúratudatos összehasonlítók időnként viselkedésbeli módosításokat hajthatnak végre. Az ICU és a régebbi Windows NLS-eszköz is frissül, hogy figyelembe vegyék a világnyelvek változását. További információkért lásd a Területi beállítások (kultúra) adatváltozás című blogbejegyzést. Az Ordinal comparer viselkedése soha nem változik, mivel pontosan bitenkénti keresést és összehasonlítást végez. Az OrdinalIgnoreCase összehasonlító viselkedése azonban megváltozhat, mivel a Unicode egyre több karakterkészletet foglal magában, és kijavítja a meglévő burkolatadatok kihagyását.
  • Használat: Az összehasonlítók StringComparison.InvariantCulture és StringComparison.InvariantCultureIgnoreCase nyelvi összehasonlítók, amelyek nem kultúratudatosak. Ez azt jelzi, hogy ezek az összehasonlítók olyan fogalmakat ismernek, mint például az ékezetes karakter, amelynek több lehetséges mögöttes ábrázolása van, és hogy minden ilyen ábrázolás egyenlő. Azonban a nem kultúratudatos nyelvi összehasonlítók nem tartalmaznak speciális kezelést a <dz> esetében, amely különbözik a <d>-től vagy a <z>-től, ahogy az fent látható. Emellett nem lesznek olyan speciális karakterek, mint a német Eszett (ß).

A .NET az invariáns globalizációs módot is kínálja. Ez az opt-in mód letiltja a nyelvi keresési és összehasonlító rutinokkal foglalkozó kódútvonalakat. Ebben a módban minden művelet ordinali vagy OrdinalIgnoreCase viselkedést használ, függetlenül attól, hogy a hívó mit CultureInfo vagy StringComparison argumentumot biztosít. További információ: Futtatókörnyezeti konfigurációs beállítások a globalizációhoz és a .NET Core Globalization Invariant Módhoz.

Az invariáns kultúrát használó sztringműveletek

Az invariáns kultúrával való összehasonlítás a CompareInfo statikus CultureInfo.InvariantCulture tulajdonság által visszaadott tulajdonságot használja. Ez a viselkedés minden rendszeren ugyanaz; a tartományon kívüli karaktereket lefordítja egyenértékű invariáns karakterekké. Ez az irányelv hasznos lehet egy egységes karakterlánc-viselkedés fenntartásához a különböző kultúrákban, de gyakran váratlan eredményeket ad.

Az invariáns kultúrával való kis- és nagybetűs összehasonlítások a statikus CompareInfo tulajdonság által visszaadott statikus CultureInfo.InvariantCulture tulajdonságot is használják összehasonlítási információkhoz. A lefordított karakterek közötti különbségeket a rendszer figyelmen kívül hagyja.

Az ASCII-sztringeken azonos módon használt StringComparison.InvariantCulture és StringComparison.Ordinal működő összehasonlítások. Azonban olyan nyelvi döntéseket hoz, StringComparison.InvariantCulture amelyek nem feltétlenül megfelelőek a bájthalmazként értelmezendő sztringekhez. Az CultureInfo.InvariantCulture.CompareInfo objektum lehetővé teszi, hogy a Compare metódus bizonyos karakterkészleteket egyenértékűként értelmezzen. Például a következő egyenértékűség érvényes az invariáns kultúrában:

InvariantCulture: a + ̊ = å

A LATIN KIS BETŰ A karakter "a" (\u0061), amikor a "+ " ̊" karakter fölötti KOMBINÁLT GYŰRŰ (\u030a) mellett van, úgy értelmezik, mint az "å" (\u00e5) KARAKTERT TARTALMAZÓ LATIN KIS A BETŰT. Ahogy az alábbi példa is mutatja, ez a viselkedés eltér az ordinális összehasonlítástól.

string separated = "\u0061\u030a";
string combined = "\u00e5";

Console.WriteLine($"Equal sort weight of {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");

Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {string.Compare(separated, combined, StringComparison.Ordinal) == 0}");

// The example displays the following output:
//     Equal sort weight of a° and å using InvariantCulture: True
//     Equal sort weight of a° and å using Ordinal: False
Module Program
    Sub Main()
        Dim separated As String = ChrW(&H61) & ChrW(&H30A)
        Dim combined As String = ChrW(&HE5)

        Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.InvariantCulture) = 0)

        Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.Ordinal) = 0)

        ' The example displays the following output:
        '     Equal sort weight of a° and å using InvariantCulture: True
        '     Equal sort weight of a° and å using Ordinal: False
    End Sub
End Module

Ha fájlneveket, cookie-kat vagy bármi mást értelmez, ahol egy kombináció, például az "å" jelenhet meg, adinális összehasonlítások továbbra is a legátláthatóbb és legilleszthetőbb viselkedést kínálják.

Egyensúlyban az invariáns kultúra kevés olyan tulajdonsággal rendelkezik, amelyek hasznossá teszik az összehasonlítást. Nyelvi szempontból releváns összehasonlítást végez, ami megakadályozza, hogy teljes szimbolikus egyenértékűséget garantáljon, de nem ez a választás a kulturális környezetben való megjelenítéshez. Az összehasonlítás néhány oka StringComparison.InvariantCulture közé tartozik a rendezett adatok megőrzése a kulturálisan azonos megjelenítéshez. Ha például egy nagyméretű adatfájl, amely egy alkalmazáshoz tartozó rendezetlen azonosítók listáját tartalmazza, a listához való hozzáadáshoz invariáns stílusú rendezés szükséges.

Hogyan válasszunk StringComparison tagot

Az alábbi táblázat a szemantikai sztringkörnyezet és az enumeráció StringComparison eleme közötti leképezést ismerteti.

Adat Magatartás Megfelelő System.StringComparison

érték
Kis- és nagybetűket megkülönböztető belső azonosítók.

Kis- és nagybetűket megkülönböztető azonosítók olyan szabványokban, mint az XML és a HTTP.

A kis- és nagybetűkre érzékeny biztonsági beállítások.
Nem nyelvi azonosító, ahol a bájtok pontosan egyeznek. Ordinal
Kis- és nagybetűket nem megkülönböztető belső azonosítók.

Kis- és nagybetűket nem megkülönböztető azonosítók olyan szabványokban, mint az XML és a HTTP.

Fájl elérési útjai.

Beállításkulcsok és értékek.

Környezeti változók.

Erőforrás-azonosítók (például foglalatnevek).

Esetérzékenységet nem figyelembe vevő biztonsági beállítások.
Nem nyelvi azonosító, ahol az eset irreleváns. OrdinalIgnoreCase
Néhány megőrzött, nyelvi szempontból releváns adat.

Rögzített rendezési sorrendet igénylő nyelvi adatok megjelenítése.
Kulturálisan agnosztikus adatok, amelyek még mindig nyelvi szempontból relevánsak. InvariantCulture

-vagy-

InvariantCultureIgnoreCase
A felhasználó számára megjelenített adatok.

A legtöbb felhasználói bemenet.
Helyi nyelvi szokásokat igénylő adatok. CurrentCulture

-vagy-

CurrentCultureIgnoreCase

Biztonsági következmények

Ha az alkalmazás sztring API-kat használ szűréshez vagy hozzáférés-vezérléshez, használjon sorszám-összehasonlításokat. A jelenlegi kultúrán alapuló nyelvi összehasonlítások platformonként és területi beállításonként eltérő, váratlan eredményeket hozhatnak. Az alábbihoz hasonló kódminták érzékenyek lehetnek a biztonsági résekre:

//
// THIS SAMPLE CODE IS INCORRECT.
// DO NOT USE IT IN PRODUCTION.
//
bool ContainsHtmlSensitiveCharacters(string input)
{
    if (input.IndexOf("<") >= 0) { return true; }
    if (input.IndexOf("&") >= 0) { return true; }
    return false;
}
'
' THIS SAMPLE CODE IS INCORRECT.
' DO NOT USE IT IN PRODUCTION.
'
Function ContainsHtmlSensitiveCharacters(input As String) As Boolean
    If input.IndexOf("<") >= 0 Then Return True
    If input.IndexOf("&") >= 0 Then Return True
    Return False
End Function

Mivel a string.IndexOf(string) módszer alapértelmezés szerint nyelvi keresést használ, lehetséges, hogy egy sztring literális '<' vagy '&' karaktert tartalmaz, és string.IndexOf(string) visszaadja -1 azt, amely azt jelzi, hogy a keresési részsztring nem található. A CA1307 és a CA1309 kódelemzési szabályok megjelölik az ilyen hívási helyeket, és figyelmeztetik a fejlesztőt, hogy lehetséges probléma merül fel.

Gyakori sztring-összehasonlító módszerek a .NET-ben

Az alábbi szakaszok a sztringek összehasonlítására leggyakrabban használt módszereket ismertetik.

String.Compare

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

Mivel a művelet a sztringértelmezés középpontjában áll, a metódushívások minden példányát meg kell vizsgálni annak megállapításához, hogy a sztringeket a jelenlegi kultúra szerint kell-e értelmezni, vagy (szimbolikusan) el kell-e bontani a kultúrától. Általában ez az utóbbi, és ehelyett StringComparison.Ordinal összehasonlítást kell használni.

A System.Globalization.CompareInfo osztály, amelyet a CultureInfo.CompareInfo tulajdonság ad vissza, tartalmaz egy Compare metódust is, amely a CompareOptions jelző felsorolás révén számos egyeztetési lehetőséget kínál (sorrendi, szóköz kihagyása, kana típus figyelmen kívül hagyása stb.).

String.CompareTo

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

Ez a metódus jelenleg nem kínál túlterhelést, amely típust StringComparison határoz meg. Ezt a metódust általában az ajánlott String.Compare(String, String, StringComparison) űrlapra lehet konvertálni.

A metódust IComparable megvalósító típusok és IComparable<T> interfészek implementálják ezt a módszert. Mivel nem kínál paramétert StringComparison , a implementálási típusok gyakran lehetővé teszik, hogy a felhasználó konstruktorában adjon meg egy paramétert StringComparer . Az alábbi példa egy olyan osztályt FileName határoz meg, amelynek osztálykonstruktora tartalmaz egy paramétert StringComparer . Ezt az StringComparer objektumot ezután használja a FileName.CompareTo metódus.

class FileName : IComparable
{
    private readonly StringComparer _comparer;

    public string Name { get; }

    public FileName(string name, StringComparer? comparer)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

        Name = name;

        if (comparer != null)
            _comparer = comparer;
        else
            _comparer = StringComparer.OrdinalIgnoreCase;
    }

    public int CompareTo(object? obj)
    {
        if (obj == null) return 1;

        if (obj is not FileName)
            return _comparer.Compare(Name, obj.ToString());
        else
            return _comparer.Compare(Name, ((FileName)obj).Name);
    }
}
Class FileName
    Implements IComparable

    Private ReadOnly _comparer As StringComparer

    Public ReadOnly Property Name As String

    Public Sub New(name As String, comparer As StringComparer)
        If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))

        Me.Name = name

        If comparer IsNot Nothing Then
            _comparer = comparer
        Else
            _comparer = StringComparer.OrdinalIgnoreCase
        End If
    End Sub

    Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
        If obj Is Nothing Then Return 1

        If TypeOf obj IsNot FileName Then
            Return _comparer.Compare(Name, obj.ToString())
        Else
            Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
        End If
    End Function
End Class

String.Equals

Alapértelmezett értelmezés: StringComparison.Ordinal.

Az String osztály lehetővé teszi az egyenlőség tesztelését a statikus vagy példánymetódus Equals túlterhelésének meghívásával, vagy a statikus egyenlőségi operátor használatával. A túlterhelések és az operátorok alapértelmezés szerint az ordinális összehasonlítást használják. Továbbra is azt javasoljuk, hogy használjon olyan túlterhelést, amely explicit módon meghatározza a StringComparison típust, még akkor is, ha egy ordinális összehasonlítást szeretne végrehajtani; ez megkönnyíti a kódban egy bizonyos sztringértelmezés szerinti keresést.

String.ToUpper és String.ToLower

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

Legyen óvatos a String.ToUpper() és String.ToLower() metódusok használatakor, mert a sztring kis- vagy nagybetűssé alakítása gyakran egy kis normalizációként szolgál a sztringek összehasonlításához, függetlenül attól, hogy kis- vagy nagybetűkről van szó. Ha igen, fontolja meg a kis- és nagybetűket figyelmen kívül hagyó összehasonlítás használatát.

A String.ToUpperInvariant módszerek és String.ToLowerInvariant a metódusok is elérhetők. ToUpperInvariant a kis- és nagybetűk normalizálásának általános módja. A StringComparison.OrdinalIgnoreCase használatával végzett összehasonlítások viselkedési szempontból két hívás összetételét jelentik: a ToUpperInvariant alkalmazása mindkét sztringargumentumra, majd az összehasonlítás elvégzése a StringComparison.Ordinal használatával.

A túlterhelési lehetőségek egy adott kultúrában nagy- és kisbetűssé alakításra is elérhetők, ha átadjuk a módszernek azt a CultureInfo objektumot, amely az adott kultúrát képviseli.

Char.ToUpper és Char.ToLower

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

A Char.ToUpper(Char) metódusok és Char.ToLower(Char) a String.ToUpper()String.ToLower() módszerek az előző szakaszban leírthoz hasonlóan működnek.

String.StartsWith és String.EndsWith

Alapértelmezett értelmezés: StringComparison.CurrentCulture (ha az első paraméter egy string), vagy StringComparison.Ordinal (ha az első paraméter a char).

Inkonzisztencia tapasztalható abban, hogy ezeknek a metódusoknak az alapértelmezett túlterhelései hogyan hajtanak végre összehasonlításokat. Azok a túlterhelések, amelyek elfogadnak egy char paramétert, sorrend szerinti összehasonlítást végeznek, de azok, amelyek elfogadnak egy string paramétert, kultúrafüggő összehasonlítást végezhetnek, és figyelmen kívül hagyhatják a nem nyomtatható karaktereket.

String.IndexOf és String.LastIndexOf

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

Hiányzik a konzisztencia abban, hogy a metódusok alapértelmezett túlterhelései hogyan hajtanak végre összehasonlításokat. A String.IndexOf paramétert tartalmazó összes String.LastIndexOf és Char metódus sorszám szerinti összehasonlítást hajt végre, de az alapértelmezett String.IndexOf és String.LastIndexOf metódusok, amelyek tartalmazzák a String paramétert, kultúraérzékeny összehasonlítást végeznek.

Ha meghívja a String.IndexOf(String) vagy String.LastIndexOf(String) metódusokat, és egy sztringet ad meg az aktuális példányban való kereséshez, javasoljuk, hogy hívjon meg egy StringComparison túlterhelést, amely explicit módon meghatározza a típust. Az argumentumot Char tartalmazó túlterhelések nem teszik lehetővé a típus megadását StringComparison .

String.Contains

Alapértelmezett értelmezés: StringComparison.Ordinal.

Ellentétben String.IndexOfString.Contains metódussal, amely alapértelmezés szerint egy sorszám szerinti összehasonlítást alkalmaz mind a char, mind a string túlterheléseihez. Azonban, amikor a szándék fontos, továbbra is át kell adnia egy explicit StringComparison argumentumot, hogy a viselkedés egyértelmű legyen a hívási helyen.

MemoryExtensions.AsSpan.IndexOfAny és a SearchValues<T> típus

A .NET 8 bevezette a SearchValues<T> típust, amely optimalizált megoldást kínál adott karakterkészletek vagy bájtok keresésére a spanokon belül.

Ha egy sztringet ismétlődően összehasonlít egy rögzített ismert értékkészlettel, érdemes lehet láncolt összehasonlítások vagy LINQ-alapú megközelítések helyett a SearchValues<T>.Contains(T) metódust használni. SearchValues<T> a belső keresési struktúrákat előre tudja konkompatitálni, és optimalizálni az összehasonlítási logikát a megadott értékek alapján. A teljesítménybeli előnyök megtekintéséhez hozza létre és gyorsítótárazza a SearchValues<string> példányt egyszer, majd használja újra összehasonlításhoz:

using System.Buffers;

namespace ExampleCode;

internal partial class DemoCode
{
    private static readonly SearchValues<string> Commands =
        SearchValues.Create(
            ["start", "run", "go", "begin", "commence"],
            StringComparison.OrdinalIgnoreCase);

    void ProcessCommand(string command)
    {
        if (Commands.Contains(command))
        {
            // ...
        }
    }
}
Imports System.Buffers

Namespace ExampleCode
    Partial Friend Class DemoCode

        Private Shared ReadOnly Commands As SearchValues(Of String) =
            SearchValues.Create(
                {"start", "run", "go", "begin", "commence"},
                StringComparison.OrdinalIgnoreCase)

        Sub ProcessCommand(command As String)
            If Commands.Contains(command) Then
                ' ...
            End If
        End Sub

    End Class
End Namespace

A .NET 9-ben kibővítették a SearchValues, hogy támogassa az alsztringek keresését egy nagyobb sztringben. Például lásd SearchValues a bővítést.

Sztring-összehasonlítást közvetetten végző metódusok

Egyes nem sztringalapú metódusok, amelyek központi műveletként sztring-összehasonlítással rendelkeznek, a típust használják StringComparer . Az StringComparer osztály hat statikus tulajdonságot tartalmaz, amelyek olyan példányokat ad vissza StringComparer , amelyek StringComparer.Compare metódusai a következő sztring-összehasonlításokat hajtják végre:

Array.Sort és Array.BinarySearch

Alapértelmezett értelmezés: StringComparison.CurrentCulture.

Ha egy gyűjteményben tárol adatokat, vagy egy fájlból vagy adatbázisból származó tárolt adatokat olvas be gyűjteménybe, az aktuális kultúra váltása érvénytelenítheti a gyűjteményben lévő állandókat. A Array.BinarySearch metódus feltételezi, hogy a keresendő tömb elemei már rendezve vannak. A tömb bármely sztringelemének rendezéséhez a Array.Sort metódus meghívja a metódust String.Compare az egyes elemek rendezésére. A kultúraérzékeny összehasonlító használata veszélyes lehet, ha a kultúra megváltozik az array rendezése és a tartalom keresése között. Az alábbi kódban például a tárolás és a lekérés a tulajdonság által Thread.CurrentThread.CurrentCulture implicit módon biztosított összehasonlítón működik. Ha a kultúra megváltozhat a StoreNames és DoesNameExist közötti hívások során, és különösen, ha a tömb tartalma valahol a két metódushívás között megőrződik, a bináris keresés sikertelen lehet.

// Incorrect
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function

Az alábbi példában egy javasolt változat jelenik meg, amely ugyanazt a kultúrafüggetlen összehasonlító módszert használja a tömb rendezéséhez és kereséséhez. A változáskód a címkézett Line A sorokban és Line B a két példában is megjelenik.

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function

Ha ezek az adatok megmaradnak és átkerülnek a kultúrák között, és a rendszer rendezéssel jeleníti meg ezeket az adatokat a felhasználónak, érdemes lehet használni StringComparison.InvariantCulture, amely nyelvileg működik a jobb felhasználói teljesítmény érdekében, de a kultúra változásai nem érintik. Az alábbi példa módosítja az előző két példát, hogy az invariáns kultúrát használja a tömb rendezéséhez és kereséséhez.

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function

Gyűjtemények például: Hashtable konstruktor

A sztringek kivonatolása egy másik példája egy olyan műveletnek, amelyet a sztringek összehasonlítása befolyásol.

Az alábbi példa egy Hashtable objektumot példányosít azáltal, hogy átadja neki a StringComparer tulajdonság által visszaadott StringComparer.OrdinalIgnoreCase objektumot. Mivel egy StringComparer-ből származó osztályStringComparer az IEqualityComparer interfészt implementálja, ezért a GetHashCode metódusa a hash-táblában lévő karakterláncok kivonatkódjának kiszámítására szolgál.

using System.IO;
using System.Collections;

const int InitialCapacity = 100;

Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();

// Fill the hash table
PopulateFileTable(directoryToProcess);

// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
    PrintCreationTime(file.ToUpper());


void PopulateFileTable(string directory)
{
    foreach (string file in Directory.GetFiles(directory))
        creationTimeByFile.Add(file, File.GetCreationTime(file));
}

void PrintCreationTime(string targetFile)
{
    object? dt = creationTimeByFile[targetFile];

    if (dt is DateTime value)
        Console.WriteLine($"File {targetFile} was created at time {value}.");
    else
        Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO

Module Program
    Const InitialCapacity As Integer = 100

    Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
    Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()

    Sub Main()
        ' Fill the hash table
        PopulateFileTable(s_directoryToProcess)

        ' Get some of the files and try to find them with upper cased names
        For Each File As String In Directory.GetFiles(s_directoryToProcess)
            PrintCreationTime(File.ToUpper())
        Next
    End Sub

    Sub PopulateFileTable(directoryPath As String)
        For Each file As String In Directory.GetFiles(directoryPath)
            s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
        Next
    End Sub

    Sub PrintCreationTime(targetFile As String)
        Dim dt As Object = s_creationTimeByFile(targetFile)

        If TypeOf dt Is Date Then
            Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
        Else
            Console.WriteLine($"File {targetFile} does not exist.")
        End If
    End Sub
End Module

Példák gyűjteményekre: SortedSet<T> és List<T>.Sort

Ugyanez a területi érzékenységi probléma vonatkozik a karakterláncok rendezett gyűjteményének példányosítására vagy egy meglevő karakterlánc alapú gyűjtemény rendezésére. Mindig adjon meg explicit összehasonlítót:

// Words to sort
string[] values = [ "able", "ångström", "apple", "Æble",
            "Windows", "Visual Studio" ];

//
// Potentially incorrect code - behavior might vary based on locale.
//
SortedSet<string> mySet = [.. values]; // No comparer specified

List<string> list = [.. values];
list.Sort(); // No comparer specified

//
// Corrected code - uses ordinal sorting; doesn't vary by locale.
//
SortedSet<string> mySet2 = new(values, StringComparer.Ordinal);

List<string> list2 = [.. values];
list2.Sort(StringComparer.Ordinal);
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio"}

'
' Potentially incorrect code - behavior might vary based on locale.
'
Dim mySet As New SortedSet(Of String)(values) ' No comparer specified

Dim list As New List(Of String)(values)
list.Sort() ' No comparer specified

'
' Corrected code - uses ordinal sorting; doesn't vary by locale.
'
Dim mySet2 As New SortedSet(Of String)(values, StringComparer.Ordinal)

Dim list2 As New List(Of String)(values)
list2.Sort(StringComparer.Ordinal)

Különbségek a .NET és a .NET-keretrendszer között

A .NET és a .NET-keretrendszer eltérően kezeli a globalizációt. A windowsos .NET-keretrendszer az operációs rendszer nemzeti nyelvi támogatási (NLS) eszközét használja a nyelvi sztringek összehasonlításához. A .NET az International Components for Unicode (ICU) kódtárat használja a nyelvi sztringek összehasonlításához az összes támogatott platformon.

Mivel az ICU és az NLS eltérő logikát implementál a nyelvi összehasonlítókban, a kultúraérzékeny összehasonlítást használó sztringmetóterek eredményei eltérhetnek a .NET és a .NET-keretrendszer között. Ez minden olyan módszer esetében számít, amely alapértelmezés szerint nyelvi összehasonlítót használ, beleértve a következőket:

Megjegyzés

Ez nem az érintett API-k teljes listája.

Az egyik jelentős különbség a beágyazott null karakter és más vezérlőkarakterek kezelése. Ha az NLS alatt használ nyelvi összehasonlítót, bizonyos vezérlőkarakterek, például a null karakter (\0) bizonyos összehasonlító kontextusokban figyelmen kívül hagyhatók. Az ICU keretrendszerben ezek a karakterek a sztring tényleges karaktereiként vannak kezelve. Ez eltérő string.IndexOf(string) eredményeket eredményezhet, ha a keresési sztring null karaktert tartalmaz.

A következő kód például az aktuális futtatókörnyezettől függően eltérő választ adhat:

const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");

// The snippet prints:
//
// '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
// '0' when running on .NET 5 or later (Windows)
// '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
// '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)
Const greeting As String = "Hel" & vbNullChar & "lo"
Console.WriteLine($"{greeting.IndexOf(CStr(vbNullChar))}")

' The snippet prints:
'
' '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
' '0' when running on .NET 5 or later (Windows)
' '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
' '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)

A platformfüggetlen és implementációk közötti meglepetések elkerülésének legjobb módja, ha mindig explicit StringComparison argumentumot ad át a sztring-összehasonlító módszereknek, és a nem nyelvi összehasonlításokhoz a StringComparison.Ordinal vagy StringComparison.OrdinalIgnoreCase-t használja.

Ha egy alkalmazást a .NET-keretrendszerből a .NET-be migrál, és a Windows régi NLS-viselkedéseire támaszkodik, konfigurálhatja az alkalmazást az NLS használatára. További információ: .NET-globalizáció és ICU.

Lásd még