Megosztás a következőn keresztül:


Ajánlott eljárások sztringek összehasonlítása 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 sztringek rendezése és összehasonlítása azonban nem mindig kulturális szempontból é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.

sztringhasználat Javaslatok

Ha a .NET-tel fejleszt, kövesse ezeket a javaslatokat a sztringek összehasonlításakor.

Tipp.

A sztringgel 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.
  • A 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 metódus túlterhelését CompareTo , és ne tesztelje a nulla visszatérési értéket annak megállapításához, hogy két sztring egyenlő-e.

Sztring-összehasonlítások explicit megadása

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úrával.
CurrentCultureIgnoreCase Kis- és nagybetűk érzéketlen összehasonlítását hajtja végre az aktuális kultúra használatával.
InvariantCulture Kis- és nagybetűket megkülönböztető összehasonlítást végez az invariáns kultúra használatával.
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űk érzéketlen összehasonlítása.

A metódus például IndexOf , amely egy karakternek vagy sztringnek megfelelő objektumban String lévő részsztring indexét adja vissza, 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:

  • Egyes alapértelmezett paraméterekkel (amelyek a sztringpéldányban Char keresnek) túlterhelések végeznek sorszám-összehasonlítást, míg mások (amelyek a sztringpéldányban keresnek sztringet) kulturális szempontból é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 sorszámát vagy nyelvi összehasonlítását szánta-e, vagy a "https" és a "https" közötti kis- és nagybetűk közötti url.Scheme különbség okozhatja 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ó értelmezés, hogy egyik sztring sem kisebb, mint a másik. 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.

Feljegyzé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. További információ: Sztringek és Unicode Standard.

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

Az egyik feltétel az aktuális kultúra konvencióit használja a sztringek összehasonlítása során. 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, az alapértelmezés szerint az operációs rendszer beállítása. 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-Standard kiadás") kultúrák közötti rendezési sorrend különbségeit szemlélteti. 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

Az aktuális kultúrát használó kis- és nagybetűk érzéketlen összehasonlításai megegyeznek a kultúraérzékeny összehasonlításokkal, azzal a különbségtel, hogy figyelmen kívül hagyják a szál aktuális kultúrája által diktált kis- és nagybetűket. Ez a viselkedés rendezési sorrendben is nyilvánulhat meg.

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 canonical példa a török-I probléma.

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 verziója. A török tartalmaz egy kisbetűs "i without a dot" (ı) (\u0131) karaktert is, amely az "I" nagybetűt használja. 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 nem nyelvi jellegűek, az alapértelmezett túlterhelések nem kívánt eredményeket eredményezhetnek, mivel az alábbi kísérlet a "bill" és a "BILL" sztringek kis- és nagybetűk érzéketlen összehasonlítását mutatja be.

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ámi sztringműveletek

A metódushívásban szereplő StringComparison.Ordinal érték megadása StringComparison.OrdinalIgnoreCase nem nyelvi összehasonlítást jelent, amelyben a természetes nyelvek jellemzői figyelmen kívül lesznek hagyva. 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 sztring-összehasonlítások, amelyekben az egyes sztringek minden bájtját nyelvi értelmezés nélkül hasonlítják össze; Például a "windows" nem egyezik a "Windows" értékkel. 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 sztringek pontosan egyezzenek meg, vagy konzervatív egyeztetési szabályzatot 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 .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. A beágyazott nem nyomtatható karakterek kihagyhatók a sztring-összehasonlító módszerek, például String.StartsWitha .

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, például String.Containsa , String.EndsWith, String.IndexOfString.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 a sorszám-összehasonlítás használatakor, ahogy az alábbi példa is 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űk érzéketlen összehasonlítása 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) összes 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 megjelennek.

Az ordinális szemantika az argumentumot nem tartalmazó túlterhelések (beleértve az egyenlőségi operátortStringComparison) alapértelmezett String.Equals értéke. Mindenesetre azt javasoljuk, hogy egy paraméterrel rendelkező StringComparison túlterhelést hívjon meg.

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 a szabályzat hasznos lehet a különböző kultúrák sztring-viselkedésének fenntartásához, de gyakran váratlan eredményt ad.

Az invariáns kultúrával való kis- és nagybetűs összehasonlítások a statikus CultureInfo.InvariantCulture tulajdonság által visszaadott statikus CompareInfo 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), ha a "+ " ̊" (\u030a) karakter fölötti KOMBINÁLT GYŰRŰ mellett van, akkor a LATIN KIS BETŰ A KARAKTER "å" karakter fölötti gyűrűvel (\u00e5) értelmezve. 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 {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
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.

StringComparison-tag kiválasztása a metódushíváshoz

Az alábbi táblázat a szemantikai sztringkörnyezet és az enumerálási StringComparison tag leképezését ismerteti:

Adatok Működé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.

Kis- és nagybetűkre vonatkozó 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 leírónevek).

Kis- és nagybetűkkel kapcsolatos 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

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 inkább összehasonlítást StringComparison.Ordinal kell használni.

A System.Globalization.CompareInfo tulajdonság által CultureInfo.CompareInfo visszaadott osztály olyan metódust Compare is tartalmaz, amely a jelző számbavétele révén CompareOptions számos egyeztetési lehetőséget biztosít (sorszám, üres terület figyelmen kívül hagyá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 azonban, hogy túlterhelést hívjon, amely explicit módon meghatározza a StringComparison típust, még akkor is, ha egy sorszám-összehasonlítást szeretne végrehajtani; ez megkönnyíti a kód keresését egy bizonyos sztringértelmezéshez.

String.ToUpper és String.ToLower

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

Ügyeljen arra, hogy a metódusok és String.ToLower() a String.ToUpper() metódusok használata során a sztringeket kis- vagy nagybetűkre kényszerítse, mert a sztringek kis normalizálása a sztringek kisbetűs összehasonlítása esetén, esettől függetlenül. Ha igen, fontolja meg a kis- és nagybetűk érzéketlen összehasonlításá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 szokásos módja. A használt StringComparison.OrdinalIgnoreCase összehasonlítások viselkedési szempontból két hívás összetétele: mindkét sztringargumentum meghívása ToUpperInvariant és az összehasonlítás a használatával StringComparison.Ordinal.

A túlterhelések egy adott kultúrában nagy- és kisbetűssé alakíthatók úgy is, hogy átadnak egy CultureInfo objektumot, amely az adott kultúrát jelöli a metódusnak.

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.

Alapértelmezés szerint mindkét módszer kultúraérzékeny összehasonlítást végez. Különösen 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 paramétert tartalmazó összes String.IndexOf és String.LastIndexOf metódus végrehajtja a sorszám-összehasonlítást, de az alapértelmezett String.IndexOf és String.LastIndexOf a paramétert String tartalmazó metódusok kultúraérzékeny összehasonlítást végeznek.Char

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

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 and 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 a tömb rendezése és tartalma között változik. 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 hívás és StoreNamesDoesNameExista – különösen akkor, ha a tömb tartalma valahol a két metódushívás között megmarad –, 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 rendezési (kulturális érzéketlenség) ö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

Példák gyűjteményekre: Hashtable konstruktor

A kivonatolási sztringek egy második példát nyújtanak egy olyan műveletre, amelyet a sztringek összehasonlítása érint.

Az alábbi példa a tulajdonság által StringComparer.OrdinalIgnoreCase visszaadott objektum átadásával StringComparer példányosít egy Hashtable objektumot. Mivel egy, az IEqualityComparer interfész implementálásából StringComparer származó osztályStringComparer, a metódusa GetHashCode a kivonattáblában lévő sztringek 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

Lásd még