Delen via


Aanbevolen procedures voor het vergelijken van tekenreeksen in .NET

.NET biedt uitgebreide ondersteuning voor het ontwikkelen van gelokaliseerde en geglobaliseerde toepassingen en maakt het eenvoudig om de conventies van de huidige cultuur of een specifieke cultuur toe te passen bij het uitvoeren van algemene bewerkingen, zoals het sorteren en weergeven van tekenreeksen. Het sorteren of vergelijken van tekenreeksen is echter niet altijd een cultuurgevoelige bewerking. Tekenreeksen die intern door een toepassing worden gebruikt, moeten bijvoorbeeld in alle culturen identiek worden verwerkt. Wanneer cultureel onafhankelijke tekenreeksgegevens, zoals XML-tags, HTML-tags, gebruikersnamen, bestandspaden en de namen van systeemobjecten, worden geïnterpreteerd alsof ze cultuurgevoelig zijn, kan toepassingscode onderhevig zijn aan subtiele bugs, slechte prestaties en, in sommige gevallen, beveiligingsproblemen.

In dit artikel worden de methoden voor het sorteren, vergelijken en hoofdletters van tekenreeksen in .NET onderzocht, worden aanbevelingen weergegeven voor het selecteren van een geschikte methode voor het verwerken van tekenreeksen en aanvullende informatie over methoden voor het verwerken van tekenreeksen.

Aanbevelingen voor tekenreeksgebruik

Wanneer u met .NET ontwikkelt, volgt u deze aanbevelingen wanneer u tekenreeksen vergelijkt.

Tip

Verschillende tekenreeksgerelateerde methoden voeren een vergelijking uit. Voorbeelden zijn onder andere String.Equals, String.Compare, String.IndexOfen String.StartsWith.

Vermijd de volgende procedures wanneer u tekenreeksen vergelijkt:

  • Gebruik geen overbelastingen die niet expliciet of impliciet de tekenreeksvergelijkingsregels voor tekenreeksbewerkingen opgeven.
  • Gebruik in de meeste gevallen geen tekenreeksbewerkingen op StringComparison.InvariantCulture basis van. Een van de weinige uitzonderingen is wanneer u taalkundig zinvolle maar cultureel agnostische gegevens bewaart.
  • Gebruik geen overbelasting van de String.Compare of CompareTo methode en test voor een retourwaarde van nul om te bepalen of twee tekenreeksen gelijk zijn.

Tekenreeksvergelijkingen expliciet opgeven

De meeste bewerkingsmethoden voor tekenreeksen in .NET zijn overbelast. Normaal gesproken accepteren een of meer overbelastingen standaardinstellingen, terwijl anderen geen standaardinstellingen accepteren en in plaats daarvan de precieze manier definiëren waarop tekenreeksen moeten worden vergeleken of gemanipuleerd. De meeste methoden die niet afhankelijk zijn van standaardwaarden, bevatten een parameter van het type StringComparison, een opsomming die expliciet regels voor tekenreeksvergelijking op basis van cultuur en hoofdlettergebruik specificeert. In de volgende tabel worden de StringComparison opsommingsleden beschreven.

StringComparison-lid Beschrijving
CurrentCulture Voert een hoofdlettergevoelige vergelijking uit met behulp van de huidige cultuur.
CurrentCultureIgnoreCase Voert een niet-hoofdlettergevoelige vergelijking uit met behulp van de huidige cultuur.
InvariantCulture Voert een hoofdlettergevoelige vergelijking uit met behulp van de invariante cultuur.
InvariantCultureIgnoreCase Voert een niet-hoofdlettergevoelige vergelijking uit met behulp van de invariante cultuur.
Ordinal Voert een rangtelvergelijking uit.
OrdinalIgnoreCase Hiermee wordt een niet-hoofdlettergevoelige ordinale vergelijking uitgevoerd.

De methode, die bijvoorbeeld IndexOf de index van een subtekenreeks retourneert in een String object dat overeenkomt met een teken of tekenreeks, heeft negen overbelastingen:

We raden u aan om om de volgende redenen een overbelasting te selecteren die geen standaardwaarden gebruikt:

  • Sommige overbelastingen met standaardparameters (die zoeken naar een Char in het tekenreeksexemplaren) voeren een rangtelvergelijking uit, terwijl andere (die zoeken naar een tekenreeks in het tekenreeksexemplaren) cultuurgevoelig zijn. Het is moeilijk om te onthouden welke methode welke standaardwaarde gebruikt en gemakkelijk om de overbelastingen te verwarren.

  • De intentie van de code die afhankelijk is van standaardwaarden voor methodeaanroepen, is niet duidelijk. In het volgende voorbeeld, dat afhankelijk is van standaardinstellingen, is het moeilijk om te weten of de ontwikkelaar daadwerkelijk een rangtelwoord of een taalkundige vergelijking van twee tekenreeksen heeft bedoeld, of dat een caseverschil tussen url.Scheme en https de test voor gelijkheid kan veroorzaken 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
    

Over het algemeen raden we u aan een methode aan te roepen die niet afhankelijk is van standaardwaarden, omdat het de bedoeling van de code ondubbelzinnig maakt. Dit maakt de code op zijn beurt beter leesbaar en eenvoudiger te detecteren en te onderhouden. In het volgende voorbeeld worden de vragen behandeld die zijn gesteld over het vorige voorbeeld. Het maakt duidelijk dat ordinale vergelijking wordt gebruikt en dat verschillen in het geval van negeren worden genegeerd.

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

De details van tekenreeksvergelijking

Vergelijking van tekenreeksen is het hart van veel tekenreeksgerelateerde bewerkingen, met name sorteren en testen op gelijkheid. Tekenreeksen sorteren in een bepaalde volgorde: Als 'mijn' wordt weergegeven voor 'tekenreeks' in een gesorteerde lijst met tekenreeksen, moet 'mijn' kleiner dan of gelijk zijn aan 'tekenreeks'. Bovendien definieert vergelijking impliciet gelijkheid. De vergelijkingsbewerking retourneert nul voor tekenreeksen die gelijk zijn. Een goede interpretatie is dat geen van beide tekenreeksen kleiner is dan de andere. De meeste zinvolle bewerkingen met tekenreeksen omvatten een of beide van deze procedures: vergelijken met een andere tekenreeks en het uitvoeren van een goed gedefinieerde sorteerbewerking.

Notitie

U kunt de sorteergewichttabellen downloaden, een set tekstbestanden die informatie bevatten over de tekengewichten die worden gebruikt in sorteer- en vergelijkingsbewerkingen voor Windows-besturingssystemen, en de standaardtabel Unicode-sorteringselement, de nieuwste versie van de sorteergewichttabel voor Linux en macOS. De specifieke versie van de sorteergewichttabel in Linux en macOS is afhankelijk van de versie van de internationale onderdelen voor Unicode-bibliotheken die op het systeem zijn geïnstalleerd. Zie ICU downloaden voor informatie over ICU-versies en de Unicode-versies die ze implementeren.

Het evalueren van twee tekenreeksen voor gelijkheid of sorteervolgorde levert echter geen enkel, correct resultaat op; het resultaat is afhankelijk van de criteria die worden gebruikt om de tekenreeksen te vergelijken. Met name tekenreeksvergelijkingen die ordinaal zijn of die zijn gebaseerd op de casing- en sorteerconventies van de huidige cultuur of de invariante cultuur (een landinstellingsagnostische cultuur op basis van de Engelse taal) kunnen verschillende resultaten opleveren.

Daarnaast kunnen tekenreeksvergelijkingen met verschillende versies van .NET of .NET op verschillende besturingssystemen of besturingssysteemversies verschillende resultaten retourneren. Zie Tekenreeksen en de Unicode-standaard voor meer informatie.

Tekenreeksvergelijkingen die gebruikmaken van de huidige cultuur

Een criterium omvat het gebruik van de conventies van de huidige cultuur bij het vergelijken van tekenreeksen. Vergelijkingen die zijn gebaseerd op de huidige cultuur, gebruiken de huidige cultuur of landinstelling van de thread. Als de cultuur niet door de gebruiker is ingesteld, wordt deze standaard ingesteld op de instelling van het besturingssysteem. U moet altijd vergelijkingen gebruiken die zijn gebaseerd op de huidige cultuur wanneer gegevens taalkundig relevant zijn en wanneer deze een cultuurgevoelige gebruikersinteractie weerspiegelen.

Vergelijkings- en hoofdlettergedrag in .NET verandert echter wanneer de cultuur verandert. Dit gebeurt wanneer een toepassing wordt uitgevoerd op een computer met een andere cultuur dan de computer waarop de toepassing is ontwikkeld, of wanneer de uitvoeringsthread de cultuur wijzigt. Dit gedrag is opzettelijk, maar het blijft niet duidelijk voor veel ontwikkelaars. In het volgende voorbeeld ziet u de verschillen in sorteervolgorde tussen de V.S. Engels ('en-US') en de Zweedse (sv-SE)-culturen. Houd er rekening mee dat de woorden 'ångström', 'Windows' en 'Visual Studio' worden weergegeven op verschillende posities in de gesorteerde tekenreeksmatrices.

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

Hoofdlettergevoelige vergelijkingen die gebruikmaken van de huidige cultuur zijn hetzelfde als cultuurgevoelige vergelijkingen, behalve dat ze hoofdlettergevoelige vergelijkingen negeren zoals bepaald door de huidige cultuur van de thread. Dit gedrag kan zich ook in sorteervolgordes voordoen.

Vergelijkingen die gebruikmaken van de huidige cultuursemantiek zijn de standaardinstelling voor de volgende methoden:

In elk geval raden we u aan een overbelasting aan te roepen die een StringComparison parameter heeft om de intentie van de methodeaanroep duidelijk te maken.

Subtiele en niet zo subtiele bugs kunnen ontstaan wanneer niet-taalkundige tekenreeksgegevens taalkundig worden geïnterpreteerd of wanneer tekenreeksgegevens uit een bepaalde cultuur worden geïnterpreteerd met behulp van de conventies van een andere cultuur. Het canonieke voorbeeld is het Turks-I-probleem.

Voor bijna alle Latijnse alfabetten, waaronder Amerikaans Engels, is het teken "i" (\u0069) de kleine letter van het teken "I" (\u0049). Deze behuizingsregel wordt snel de standaardinstelling voor iemand die in een dergelijke cultuur programmeert. Het Turkse alfabet ("tr-TR") bevat echter een "I met een punt" teken "İ" (\u0130), de hoofdversie van "i". Turks bevat ook een kleine letter "i zonder punt" teken, "ı" (\u0131), dat in hoofdletters staat op "I". Dit gedrag doet zich ook voor in de Azerbeidzjaanse cultuur (az).

Daarom zijn veronderstellingen over het hoofdlettergebruik "i" of het verlagen van "I" niet geldig in alle culturen. Als u de standaardoverbelastingen gebruikt voor tekenreeksvergelijkingsroutines, worden deze onderworpen aan afwijkingen tussen culturen. Als de te vergelijken gegevens niet-taalkundig zijn, kan het gebruik van de standaardoverbelasting ongewenste resultaten opleveren, omdat de volgende poging om een niet-hoofdlettergevoelige vergelijking van de tekenreeksen 'bill' en 'BILL' uit te voeren.

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

Deze vergelijking kan aanzienlijke problemen veroorzaken als de cultuur per ongeluk wordt gebruikt in beveiligingsgevoelige instellingen, zoals in het volgende voorbeeld. Een methode-aanroep zoals IsFileURI("file:") retourneert true als de huidige cultuur Amerikaans Engels is, maar false als de huidige cultuur Turks is. Op Turkse systemen kan iemand dus beveiligingsmaatregelen omzeilen die de toegang tot niet-hoofdlettergevoelige URI's blokkeren die beginnen met 'FILE:'.

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

Omdat 'bestand:' in dit geval bedoeld is om te worden geïnterpreteerd als een niet-taalkundige, cultuurgevoelige id, moet de code in plaats daarvan worden geschreven zoals wordt weergegeven in het volgende voorbeeld:

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

Ordinale tekenreeksbewerkingen

Als u de StringComparison.Ordinal of StringComparison.OrdinalIgnoreCase waarde in een methodeoproep opgeeft, wordt een niet-taalkundige vergelijking aangegeven waarin de kenmerken van natuurlijke talen worden genegeerd. Methoden die worden aangeroepen met deze StringComparison waarden basisreeksbewerkingsbeslissingen voor eenvoudige bytevergelijkingen in plaats van casing- of equivalentietabellen die worden geparameteriseerd door cultuur. In de meeste gevallen past deze benadering het beste bij de beoogde interpretatie van tekenreeksen, terwijl code sneller en betrouwbaarder wordt.

Ordinale vergelijkingen zijn tekenreeksvergelijkingen waarin elke byte van elke tekenreeks wordt vergeleken zonder taalkundige interpretatie; 'windows' komt bijvoorbeeld niet overeen met 'Windows'. Dit is in wezen een aanroep van de C Runtime-functie strcmp . Gebruik deze vergelijking wanneer de context bepaalt dat tekenreeksen exact moeten worden vergeleken of een conservatief overeenkomend beleid vereist. Daarnaast is ordinale vergelijking de snelste vergelijkingsbewerking, omdat er geen taalkundige regels worden toegepast bij het bepalen van een resultaat.

Tekenreeksen in .NET kunnen ingesloten null-tekens bevatten (en andere niet-afdrukbare tekens). Een van de duidelijkste verschillen tussen ordinale en cultuurgevoelige vergelijking (inclusief vergelijkingen die de invariante cultuur gebruiken) betreft de verwerking van ingesloten null-tekens in een tekenreeks. Deze tekens worden genegeerd wanneer u de String.Compare en String.Equals methoden gebruikt om cultuurgevoelige vergelijkingen uit te voeren (inclusief vergelijkingen die gebruikmaken van de invariante cultuur). Als gevolg hiervan kunnen tekenreeksen die ingesloten null-tekens bevatten, worden beschouwd als gelijk aan tekenreeksen die dat niet doen. Ingesloten niet-afdrukbare tekens kunnen worden overgeslagen voor tekenreeksvergelijkingsmethoden, zoals String.StartsWith.

Belangrijk

Hoewel tekenreeksvergelijkingsmethoden geen rekening houden met ingesloten null-tekens, tekenreekszoekmethoden zoals String.Contains, String.IndexOfString.EndsWith, en String.LastIndexOfString.StartsWith niet.

In het volgende voorbeeld wordt een cultuurgevoelige vergelijking van de tekenreeks Aa uitgevoerd met een vergelijkbare tekenreeks die meerdere ingesloten null-tekens bevat tussen A en a, en laat zien hoe de twee tekenreeksen als gelijk worden beschouwd:

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

De tekenreeksen worden echter niet als gelijk beschouwd wanneer u rangtelvergelijking gebruikt, zoals in het volgende voorbeeld wordt weergegeven:

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

Hoofdlettergevoelige ordinale vergelijkingen zijn de eerstvolgende meest conservatieve benadering. Deze vergelijkingen negeren de meeste behuizingen; 'windows' komt bijvoorbeeld overeen met 'Windows'. Bij het omgaan met ASCII-tekens is dit beleid gelijk aan StringComparison.Ordinal, behalve dat het de gebruikelijke ASCII-behuizing negeert. Daarom komt elk teken in [A, Z] (\u0041-\u005A) overeen met het bijbehorende teken in [a,z] (\u0061-\007A). Casing buiten het ASCII-bereik maakt gebruik van de tabellen van de invariante cultuur. Daarom volgt de volgende vergelijking:

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

is gelijk aan (maar sneller dan) deze vergelijking:

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

Deze vergelijkingen zijn nog steeds zeer snel.

Zowel StringComparison.Ordinal als StringComparison.OrdinalIgnoreCase de binaire waarden rechtstreeks gebruiken en zijn het meest geschikt voor overeenkomende waarden. Als u niet zeker weet wat de vergelijkingsinstellingen zijn, gebruikt u een van deze twee waarden. Omdat ze echter een byte-bytevergelijking uitvoeren, sorteren ze niet op een taalkundige sorteervolgorde (zoals een Engelse woordenlijst), maar op een binaire sorteervolgorde. De resultaten kunnen er in de meeste contexten vreemd uitzien als ze worden weergegeven voor gebruikers.

Ordinale semantiek is de standaardinstelling voor String.Equals overbelastingen die geen argument bevatten StringComparison (inclusief de gelijkheidsoperator). In elk geval raden we u aan een overbelasting aan te roepen die een StringComparison parameter heeft.

Tekenreeksbewerkingen die gebruikmaken van de invariante cultuur

Vergelijkingen met de invariante cultuur gebruiken de CompareInfo eigenschap die wordt geretourneerd door de statische CultureInfo.InvariantCulture eigenschap. Dit gedrag is hetzelfde op alle systemen; hiermee worden alle tekens buiten het bereik omgezet in wat ze geloven in equivalente invariante tekens. Dit beleid kan handig zijn voor het onderhouden van één reeks tekenreeksgedrag in verschillende culturen, maar het biedt vaak onverwachte resultaten.

Hoofdlettergevoelige vergelijkingen met de invariante cultuur gebruiken ook de statische CompareInfo eigenschap die door de statische CultureInfo.InvariantCulture eigenschap wordt geretourneerd voor vergelijkingsinformatie. Eventuele verschillen tussen deze vertaalde tekens worden genegeerd.

Vergelijkingen die identiek worden gebruikt StringComparison.InvariantCulture en StringComparison.Ordinal op ASCII-tekenreeksen werken. StringComparison.InvariantCulture Maakt echter taalkundige beslissingen die mogelijk niet geschikt zijn voor tekenreeksen die moeten worden geïnterpreteerd als een set bytes. Het CultureInfo.InvariantCulture.CompareInfo object maakt dat de Compare methode bepaalde sets tekens interpreteert als gelijkwaardig. De volgende gelijkwaardigheid is bijvoorbeeld geldig onder de invariante cultuur:

InvariantCulture: a + ̊ = å

De LATIJNSE KLEINE LETTER A teken "a" (\u0061), wordt geïnterpreteerd als de LATIJNSE KLEINE LETTER naast het teken COMBINING RING ABOVE "+ " ̊" (\u030a), wordt geïnterpreteerd als de LATIJNSE KLEINE LETTER A MET RING BOVEN teken "å" (\u00e5). Zoals in het volgende voorbeeld wordt weergegeven, verschilt dit gedrag van ordinale vergelijking.

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

Bij het interpreteren van bestandsnamen, cookies of iets anders waar een combinatie zoals "å" kan verschijnen, bieden ordinale vergelijkingen nog steeds het meest transparante en passende gedrag.

In balans heeft de invariante cultuur weinig eigenschappen die het nuttig maken voor vergelijking. Het vergelijkt een taalkundig relevante manier, waardoor het geen volledige symbolische gelijkwaardigheid garandeert, maar het is niet de keuze voor weergave in een cultuur. Een van de weinige redenen om te StringComparison.InvariantCulture vergelijken is het behouden van geordende gegevens voor een cross-cultureel identieke weergave. Als een groot gegevensbestand met een lijst met gesorteerde id's voor weergave bijvoorbeeld een toepassing bevat, is voor het toevoegen aan deze lijst een invoegbewerking met sorteren invariantstijl vereist.

Een StringComparison-lid kiezen voor uw methodeaanroep

De volgende tabel geeft een overzicht van de toewijzing van semantische tekenreekscontext aan een StringComparison opsommingslid:

Gegevens Gedrag Corresponderende System.StringComparison

waarde
Hoofdlettergevoelige interne id's.

Hoofdlettergevoelige id's in standaarden zoals XML en HTTP.

Hoofdlettergevoelige beveiligingsinstellingen.
Een niet-taalkundige id, waarbij bytes exact overeenkomen. Ordinal
Niet-hoofdlettergevoelige interne id's.

Hoofdlettergevoelige id's in standaarden zoals XML en HTTP.

Bestandspaden.

Registersleutels en -waarden.

Omgevingsvariabelen.

Resource-id's (bijvoorbeeld namen verwerken).

Niet-hoofdlettergevoelige beveiligingsinstellingen.
Een niet-taalkundige id, waarbij het geval niet relevant is. OrdinalIgnoreCase
Sommige persistente, taalkundige relevante gegevens.

Weergave van taalkundige gegevens waarvoor een vaste sorteervolgorde is vereist.
Cultureel agnostische gegevens die nog steeds taalkundig relevant zijn. InvariantCulture

– of –

InvariantCultureIgnoreCase
Gegevens die aan de gebruiker worden weergegeven.

De meeste gebruikersinvoer.
Gegevens die lokale taalkundige douane nodig hebben. CurrentCulture

– of –

CurrentCultureIgnoreCase

Algemene vergelijkingsmethoden voor tekenreeksen in .NET

In de volgende secties worden de methoden beschreven die het meest worden gebruikt voor het vergelijken van tekenreeksen.

String.Compare

Standaardinterpretatie: StringComparison.CurrentCulture.

Als de bewerking die het meest centraal staat bij de interpretatie van tekenreeksen, moeten alle exemplaren van deze methodeaanroepen worden onderzocht om te bepalen of tekenreeksen moeten worden geïnterpreteerd volgens de huidige cultuur, of worden losgekoppeld van de cultuur (symbolisch). Normaal gesproken zijn dit de laatste en moet in plaats daarvan een StringComparison.Ordinal vergelijking worden gebruikt.

De System.Globalization.CompareInfo klasse, die wordt geretourneerd door de CultureInfo.CompareInfo eigenschap, bevat ook een methode die een Compare groot aantal overeenkomende opties (rangtelwoord, negeren van witruimte, het negeren van het kana-type, enzovoort) biedt door middel van de opsomming van de CompareOptions vlag.

String.CompareTo

Standaardinterpretatie: StringComparison.CurrentCulture.

Deze methode biedt momenteel geen overbelasting die een StringComparison type aangeeft. Het is meestal mogelijk om deze methode te converteren naar het aanbevolen String.Compare(String, String, StringComparison) formulier.

Typen die de IComparable en IComparable<T> interfaces implementeren, implementeren deze methode. Omdat het niet de optie van een StringComparison parameter biedt, kunnen implementatietypen de gebruiker vaak een StringComparer in hun constructor opgeven. In het volgende voorbeeld wordt een FileName klasse gedefinieerd waarvan de klasseconstructor een StringComparer parameter bevat. Dit StringComparer object wordt vervolgens gebruikt in de FileName.CompareTo methode.

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

Standaardinterpretatie: StringComparison.Ordinal.

String Met de klasse kunt u testen op gelijkheid door de overbelasting van de statische methode of instantiemethode Equals aan te roepen, of door de operator voor statische gelijkheid te gebruiken. De overbelasting en operator gebruiken standaard rangtelwoordvergelijking. We raden u echter nog steeds aan om een overbelasting aan te roepen waarmee het type expliciet wordt opgegeven StringComparison , zelfs als u een rangtelvergelijking wilt uitvoeren. Dit maakt het gemakkelijker om code te zoeken naar een bepaalde tekenreeksinterpretatie.

String.ToUpper en String.ToLower

Standaardinterpretatie: StringComparison.CurrentCulture.

Wees voorzichtig wanneer u de String.ToUpper() en String.ToLower() methoden gebruikt, omdat het afdwingen van een tekenreeks naar hoofdletters of kleine letters vaak wordt gebruikt als een kleine normalisatie voor het vergelijken van tekenreeksen, ongeacht hoofdletters. Als dat het geval is, kunt u overwegen een niet-hoofdlettergevoelige vergelijking te gebruiken.

De String.ToUpperInvariant en String.ToLowerInvariant methoden zijn ook beschikbaar. ToUpperInvariant is de standaard manier om case te normaliseren. Vergelijkingen die worden gemaakt met behulp StringComparison.OrdinalIgnoreCase van zijn gedragsmatig de samenstelling van twee aanroepen: het aanroepen ToUpperInvariant van beide tekenreeksargumenten en het uitvoeren van een vergelijking met behulp van StringComparison.Ordinal.

Overbelastingen zijn ook beschikbaar voor conversie naar hoofdletters en kleine letters in een specifieke cultuur door een CultureInfo object dat die cultuur aan de methode vertegenwoordigt door te geven.

Char.ToUpper en Char.ToLower

Standaardinterpretatie: StringComparison.CurrentCulture.

De Char.ToUpper(Char) en Char.ToLower(Char) methoden werken op dezelfde manier als de String.ToLower()String.ToUpper() methoden die in de vorige sectie worden beschreven.

String.StartsWith en String.EndsWith

Standaardinterpretatie: StringComparison.CurrentCulture.

Standaard voeren beide methoden een cultuurgevoelige vergelijking uit. In het bijzonder kunnen ze niet-afdrukbare tekens negeren.

String.IndexOf en String.LastIndexOf

Standaardinterpretatie: StringComparison.CurrentCulture.

Er is een gebrek aan consistentie in de manier waarop de standaardoverbelastingen van deze methoden vergelijkingen uitvoeren. Alle String.IndexOf methoden die String.LastIndexOf een Char parameter bevatten, voeren een rangtelvergelijking uit, maar de standaard String.IndexOf - en String.LastIndexOf methoden die een String parameter bevatten, voeren een cultuurgevoelige vergelijking uit.

Als u de String.IndexOf(String) of String.LastIndexOf(String) methode aanroept en deze doorgeeft aan een tekenreeks om in het huidige exemplaar te zoeken, wordt u aangeraden een overbelasting aan te roepen die expliciet het StringComparison type aangeeft. Met de overbelastingen met een Char argument kunt u geen type opgeven StringComparison .

Methoden waarmee tekenreeksvergelijking indirect wordt uitgevoerd

Sommige niet-tekenreeksmethoden die een tekenreeksvergelijking als een centrale bewerking hebben, gebruiken het StringComparer type. De StringComparer klasse bevat zes statische eigenschappen die exemplaren retourneren StringComparer waarvan StringComparer.Compare de methoden de volgende typen tekenreeksvergelijkingen uitvoeren:

Array.Sort en Array.BinarySearch

Standaardinterpretatie: StringComparison.CurrentCulture.

Wanneer u gegevens in een verzameling opslaat of persistente gegevens uit een bestand of database leest in een verzameling, kan het omzetten van de huidige cultuur de invarianten in de verzameling ongeldig maken. Bij Array.BinarySearch de methode wordt ervan uitgegaan dat de elementen in de matrix die moeten worden doorzocht, al zijn gesorteerd. Als u een tekenreekselement in de matrix wilt sorteren, roept de Array.Sort methode de String.Compare methode aan om afzonderlijke elementen te orden. Het gebruik van een cultuurgevoelige vergelijking kan gevaarlijk zijn als de cultuur verandert tussen de tijd dat de matrix wordt gesorteerd en de inhoud ervan wordt doorzocht. In de volgende code werken opslag en ophalen bijvoorbeeld op de vergelijkingsfunctie die impliciet door de Thread.CurrentThread.CurrentCulture eigenschap wordt geleverd. Als de cultuur kan veranderen tussen de aanroepen naar StoreNames en DoesNameExist, en vooral als de matrixinhoud zich ergens tussen de twee methode-aanroepen bevindt, kan de binaire zoekopdracht mislukken.

// 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

In het volgende voorbeeld wordt een aanbevolen variatie weergegeven, waarbij dezelfde ordinale vergelijkingsmethode (cultuurgevoelig) wordt gebruikt om de matrix te sorteren en te doorzoeken. De wijzigingscode wordt weerspiegeld in de regels die zijn gelabeld Line A en Line B in de twee voorbeelden.

// 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

Als deze gegevens worden bewaard en verplaatst over culturen en sortering wordt gebruikt om deze gegevens aan de gebruiker te presenteren, kunt u overwegen StringComparison.InvariantCultureom , die taalkundig werkt voor betere gebruikersuitvoer, maar niet wordt beïnvloed door wijzigingen in de cultuur. In het volgende voorbeeld worden de twee vorige voorbeelden gewijzigd om de invariante cultuur te gebruiken voor het sorteren en doorzoeken van de matrix.

// 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

Voorbeeld van verzamelingen: Hashtable-constructor

Hash-tekenreeksen bieden een tweede voorbeeld van een bewerking die wordt beïnvloed door de manier waarop tekenreeksen worden vergeleken.

In het volgende voorbeeld wordt een Hashtable object geïnstitueerd door het StringComparer object door te geven dat door de StringComparer.OrdinalIgnoreCase eigenschap wordt geretourneerd. Omdat een klasse StringComparer die is afgeleid van StringComparer de interfaceGetHashCode, wordt de IEqualityComparer bijbehorende methode gebruikt om de hashcode van tekenreeksen in de hash-tabel te berekenen.

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

Zie ook