Dela via


Metodtips för att jämföra strängar i .NET

.NET ger omfattande stöd för att utveckla lokaliserade och globaliserade program och gör det enkelt att tillämpa konventionerna för den aktuella kulturen eller en specifik kultur när du utför vanliga åtgärder som sortering och visning av strängar. Men att sortera eller jämföra strängar är inte alltid en kulturkänslig åtgärd. Till exempel bör strängar som används internt av ett program vanligtvis hanteras identiskt i alla kulturer. När kulturellt oberoende strängdata, till exempel XML-taggar, HTML-taggar, användarnamn, filsökvägar och namn på systemobjekt, tolkas som om de vore kulturkänsliga kan programkod utsättas för subtila buggar, dåliga prestanda och i vissa fall säkerhetsproblem.

Den här artikeln undersöker strängsorterings-, jämförelse- och höljemetoderna i .NET, presenterar rekommendationer för att välja en lämplig stränghanteringsmetod och ger ytterligare information om stränghanteringsmetoder.

Rekommendationer för stränganvändning

När du utvecklar med .NET följer du dessa rekommendationer när du jämför strängar.

Dricks

Olika strängrelaterade metoder utför jämförelse. Exempel är String.Equals, String.Compare, String.IndexOfoch String.StartsWith.

Undvik följande metoder när du jämför strängar:

  • Använd inte överlagringar som inte uttryckligen eller implicit anger strängjämförelsereglerna för strängåtgärder.
  • Använd inte strängåtgärder baserat på StringComparison.InvariantCulture i de flesta fall. Ett av de få undantagen är när du bevarar språkligt meningsfulla men kulturellt agnostiska data.
  • Använd inte en överlagring av String.Compare metoden eller CompareTo och testa för ett returvärde på noll för att avgöra om två strängar är lika med.

Ange strängjämförelser explicit

De flesta strängmanipuleringsmetoderna i .NET är överbelastade. Vanligtvis accepterar en eller flera överlagringar standardinställningar, medan andra inte accepterar några standardvärden och i stället definierar det exakta sättet på vilket strängar ska jämföras eller manipuleras. De flesta metoder som inte förlitar sig på standardvärden innehåller en parameter av typen StringComparison, vilket är en uppräkning som uttryckligen anger regler för strängjämförelse efter kultur och skiftläge. I följande tabell beskrivs uppräkningsmedlemmarna StringComparison .

StringComparison-medlem beskrivning
CurrentCulture Utför en skiftlägeskänslig jämförelse med den aktuella kulturen.
CurrentCultureIgnoreCase Utför en skiftlägeskänslig jämförelse med den aktuella kulturen.
InvariantCulture Utför en skiftlägeskänslig jämförelse med den invarianta kulturen.
InvariantCultureIgnoreCase Utför en skiftlägeskänslig jämförelse med den invarianta kulturen.
Ordinal Utför en ordningsjämförelse.
OrdinalIgnoreCase Utför en skiftlägeskänslig ordningstalsjämförelse.

Metoden, som returnerar indexet för en delsträng i ett String objekt som matchar antingen ett tecken eller en sträng, har till exempel IndexOf nio överlagringar:

Vi rekommenderar att du väljer en överlagring som inte använder standardvärden av följande skäl:

  • Vissa överlagringar med standardparametrar (de som söker efter en Char i stränginstansen) utför en ordningsjämförelse, medan andra (de som söker efter en sträng i stränginstansen) är kulturkänsliga. Det är svårt att komma ihåg vilken metod som använder vilket standardvärde och lätt att förvirra överlagringarna.

  • Avsikten med den kod som förlitar sig på standardvärden för metodanrop är inte tydlig. I följande exempel, som förlitar sig på standardvärden, är det svårt att veta om utvecklaren faktiskt avsåg en ordningstals- eller språkjämförelse av två strängar, eller om en skiftlägesskillnad mellan url.Scheme och "https" kan leda till att testet för likhet returnerar 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
    

I allmänhet rekommenderar vi att du anropar en metod som inte förlitar sig på standardvärden, eftersom det gör avsikten med koden entydig. Detta gör i sin tur koden mer läsbar och enklare att felsöka och underhålla. I följande exempel behandlas de frågor som ställdes om föregående exempel. Det gör det tydligt att ordningstalsjämförelse används och att skillnader i fall ignoreras.

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

Information om strängjämförelse

Strängjämförelse är kärnan i många strängrelaterade åtgärder, särskilt sortering och testning för likhet. Strängar sorteras i en bestämd ordning: Om "min" visas före "strängen" i en sorterad lista med strängar måste "min" jämföra mindre än eller lika med "sträng". Dessutom definierar jämförelse implicit likhet. Jämförelseåtgärden returnerar noll för strängar som den anser vara lika med. En bra tolkning är att ingen av strängarna är mindre än den andra. De flesta meningsfulla åtgärder som omfattar strängar omfattar en eller båda dessa procedurer: jämföra med en annan sträng och köra en väldefinierad sorteringsåtgärd.

Kommentar

Du kan ladda ned sorteringsvikttabeller, en uppsättning textfiler som innehåller information om teckenvikterna som används i sorterings- och jämförelseåtgärder för Windows-operativsystem och standardtabellen för Unicode-sorteringselement, den senaste versionen av sorteringsvikttabellen för Linux och macOS. Den specifika versionen av sorteringsvikttabellen i Linux och macOS beror på vilken version av International Components for Unicode-biblioteken som är installerade i systemet. Information om ICU-versioner och Unicode-versioner som de implementerar finns i Ladda ned ICU.

Men att utvärdera två strängar för likhet eller sorteringsordning ger inte ett enda, korrekt resultat. resultatet beror på de kriterier som används för att jämföra strängarna. I synnerhet kan strängjämförelser som är ordningstal eller som baseras på hölje- och sorteringskonventionerna i den aktuella kulturen eller den invarianta kulturen (en språkvariant kultur baserad på det engelska språket) ge olika resultat.

Dessutom kan strängjämförelser med olika versioner av .NET eller användning av .NET på olika operativsystem eller operativsystemsversioner returnera olika resultat. Mer information finns i Strängar och Unicode Standard.

Strängjämförelser som använder den aktuella kulturen

Ett kriterium är att använda den aktuella kulturens konventioner vid jämförelse av strängar. Jämförelser som baseras på den aktuella kulturen använder trådens aktuella kultur eller nationella inställningar. Om kulturen inte anges av användaren, är den standardinställningen för operativsystemet. Du bör alltid använda jämförelser som baseras på den aktuella kulturen när data är språkligt relevanta och när de återspeglar kulturkänslig användarinteraktion.

Jämförelse- och höljebeteendet i .NET ändras dock när kulturen ändras. Detta inträffar när ett program körs på en dator som har en annan kultur än den dator där programmet utvecklades, eller när den körande tråden ändrar sin kultur. Det här beteendet är avsiktligt, men det är fortfarande inte uppenbart för många utvecklare. I följande exempel visas skillnader i sorteringsordning mellan kulturerna amerikansk engelska ("en-US") och svenska ("sv-SE"). Observera att orden "ångström", "Windows" och "Visual Studio" visas i olika positioner i de sorterade strängmatriserna.

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

Skiftlägeskänsliga jämförelser som använder den aktuella kulturen är desamma som kulturkänsliga jämförelser, förutom att de ignorerar skiftläge enligt trådens aktuella kultur. Det här beteendet kan också visa sig i sorteringsordningar.

Jämförelser som använder aktuell kultursemantik är standard för följande metoder:

I vilket fall som helst rekommenderar vi att du anropar en överlagring som har en StringComparison parameter för att göra avsikten med metodanropet tydlig.

Subtila och inte så subtila buggar kan uppstå när icke-språkliga strängdata tolkas språkligt, eller när strängdata från en viss kultur tolkas med hjälp av konventionerna i en annan kultur. Det kanoniska exemplet är problemet turkish-I.

För nästan alla latinska alfabet, inklusive amerikansk engelska, är tecknet "i" (\u0069) den gemena versionen av tecknet "I" (\u0049). Den här höljeregeln blir snabbt standard för någon som programmerar i en sådan kultur. Det turkiska alfabetet ("tr-TR") innehåller dock ett "I med en punkt"-tecken "İ" (\u0130), som är huvudstadsversionen av "i". Turkiska innehåller också ett gemener "i utan en punkt" tecken, "ı" (\u0131), som versaler till "I". Det här beteendet inträffar även i azerbajdzjanska kulturen ("az").

Därför är antaganden som görs om att kapitalisera "i" eller sänka "I" inte giltiga bland alla kulturer. Om du använder standardöverlagringarna för strängjämförelserutiner kommer de att omfattas av avvikelse mellan kulturer. Om de data som ska jämföras inte är språkliga kan användning av standardöverlagringar ge oönskade resultat, vilket illustreras i följande försök att göra en skiftlägeskänslig jämförelse av strängarna "bill" och "BILL".

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

Den här jämförelsen kan orsaka betydande problem om kulturen oavsiktligt används i säkerhetskänsliga inställningar, som i följande exempel. Ett metodanrop som IsFileURI("file:") returnerar true om den aktuella kulturen är amerikansk engelska, men false om den aktuella kulturen är turkisk. På turkiska system kan någon kringgå säkerhetsåtgärder som blockerar åtkomsten till skiftlägesokänsliga URI:er som börjar med "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

I det här fallet, eftersom "file:" är tänkt att tolkas som en icke-språklig, kulturokänslig identifierare, bör koden i stället skrivas enligt följande exempel:

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

Ordningstalssträngsåtgärder

Att StringComparison.Ordinal ange värdet eller StringComparison.OrdinalIgnoreCase i ett metodanrop innebär en icke-språklig jämförelse där funktionerna i naturliga språk ignoreras. Metoder som anropas med dessa StringComparison värden baserar strängåtgärdsbeslut på enkla bytejämförelser i stället för hölje- eller ekvivalenstabeller som parametriseras efter kultur. I de flesta fall passar den här metoden bäst för den avsedda tolkningen av strängar samtidigt som koden blir snabbare och mer tillförlitlig.

Ordningstalsjämförelser är strängjämförelser där varje byte av varje sträng jämförs utan språklig tolkning. Till exempel matchar "windows" inte "Windows". Det här är i princip ett anrop till C-körningsfunktionen strcmp . Använd den här jämförelsen när kontexten avgör att strängarna ska matchas exakt eller kräver en konservativ matchningsprincip. Dessutom är ordningstalsjämförelse den snabbaste jämförelseåtgärden eftersom den inte tillämpar några språkregler när ett resultat fastställs.

Strängar i .NET kan innehålla inbäddade nulltecken (och andra tecken som inte skrivs ut). En av de tydligaste skillnaderna mellan ordningstal och kulturkänslig jämförelse (inklusive jämförelser som använder den invarianta kulturen) gäller hanteringen av inbäddade null-tecken i en sträng. Dessa tecken ignoreras när du använder String.Compare metoderna och String.Equals för att utföra kulturkänsliga jämförelser (inklusive jämförelser som använder den invarianta kulturen). Därför kan strängar som innehåller inbäddade null-tecken betraktas som lika med strängar som inte gör det. Inbäddade icke-utskriftstecken kan hoppas över för strängjämförelsemetoder, till exempel String.StartsWith.

Viktigt!

Även om strängjämförelsemetoder bortser från inbäddade null-tecken, gör strängsökningsmetoder som String.Contains, String.EndsWith, String.IndexOfoch String.LastIndexOfString.StartsWith inte .

I följande exempel görs en kulturkänslig jämförelse av strängen "Aa" med en liknande sträng som innehåller flera inbäddade null-tecken mellan "A" och "a" och visar hur de två strängarna anses vara lika:

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

Strängarna anses dock inte vara lika med när du använder ordningstalsjämförelse, vilket visas i följande exempel:

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

Fallokänsliga ordningstalsjämförelser är den näst mest konservativa metoden. Dessa jämförelser ignorerar de flesta höljen; Till exempel matchar "windows" "Windows". När du hanterar ASCII-tecken motsvarar StringComparison.Ordinalden här principen , förutom att den ignorerar det vanliga ASCII-höljet. Därför matchar alla tecken i [A, Z] (\u0041-\u005A) motsvarande tecken i [a,z] (\u0061-\007A). Hölje utanför ASCII-intervallet använder tabellerna i den invarianta kulturen. Därför är följande jämförelse:

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

motsvarar (men snabbare än) den här jämförelsen:

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

Dessa jämförelser är fortfarande mycket snabba.

Både StringComparison.Ordinal och StringComparison.OrdinalIgnoreCase använder binärvärdena direkt och passar bäst för matchning. När du inte är säker på dina jämförelseinställningar använder du något av dessa två värden. Men eftersom de utför en byte-för-byte-jämförelse sorteras de inte efter en språklig sorteringsordning (som en engelsk ordlista) utan efter en binär sorteringsordning. Resultaten kan se udda ut i de flesta kontexter om de visas för användare.

Ordningssal semantik är standardvärdet för String.Equals överlagringar som inte innehåller ett StringComparison argument (inklusive likhetsoperatorn). I vilket fall som helst rekommenderar vi att du anropar en överlagring som har en StringComparison parameter.

Strängåtgärder som använder den invarianta kulturen

Jämförelser med den invarianta kulturen använder egenskapen CompareInfo som returneras av den statiska CultureInfo.InvariantCulture egenskapen. Det här beteendet är detsamma på alla system. det översätter alla tecken utanför dess intervall till vad den anser är motsvarande invarianta tecken. Den här principen kan vara användbar för att upprätthålla en uppsättning strängbeteenden i olika kulturer, men den ger ofta oväntade resultat.

Skiftlägesokänsliga jämförelser med den invarianta kulturen använder den statiska CompareInfo egenskapen som returneras av den statiska CultureInfo.InvariantCulture egenskapen även för jämförelseinformation. Eventuella skiftlägesskillnader mellan dessa översatta tecken ignoreras.

Jämförelser som använder StringComparison.InvariantCulture och StringComparison.Ordinal fungerar identiskt med ASCII-strängar. Fattar dock StringComparison.InvariantCulture språkliga beslut som kanske inte är lämpliga för strängar som måste tolkas som en uppsättning byte. Objektet CultureInfo.InvariantCulture.CompareInfo gör att Compare metoden tolkar vissa uppsättningar tecken som likvärdiga. Följande ekvivalens är till exempel giltig under den invarianta kulturen:

InvariantCulture: a + ̊ = å

LATINSK LITEN BOKSTAV Ett tecken "a" (\u0061), när det är bredvid tecknet COMBINING RING ABOVE "+ " ̊" (\u030a), tolkas som LATIN SMALL LETTER A MED RING ABOVE-tecknet "å" (\u00e5). Som följande exempel visar skiljer sig det här beteendet från ordningstalsjämförelse.

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

Vid tolkning av filnamn, cookies eller något annat där en kombination som "å" kan visas, erbjuder ordningstalsjämförelser fortfarande det mest transparenta och passande beteendet.

På det hela taget har den invarianta kulturen få egenskaper som gör den användbar för jämförelse. Den jämförs på ett språkligt relevant sätt, vilket hindrar den från att garantera fullständig symbolisk likvärdighet, men det är inte valet att visa i någon kultur. En av de få skäl som kan användas StringComparison.InvariantCulture för jämförelse är att bevara beställda data för en korskulturellt identisk visning. Om till exempel en stor datafil som innehåller en lista över sorterade identifierare för visning åtföljer ett program, skulle tillägg till den här listan kräva en infogning med invariant-format sortering.

Välja en StringComparison-medlem för ditt metodanrop

I följande tabell beskrivs mappningen från semantisk strängkontext till en StringComparison uppräkningsmedlem:

Data Funktionssätt Motsvarande System.StringComparison

värde
Skiftlägeskänsliga interna identifierare.

Skiftlägeskänsliga identifierare i standarder som XML och HTTP.

Skiftlägeskänsliga säkerhetsrelaterade inställningar.
En icke-språklig identifierare, där byte matchar exakt. Ordinal
Skiftlägesokänsliga interna identifierare.

Skiftlägesokänsliga identifierare i standarder som XML och HTTP.

Filsökvägar.

Registernycklar och -värden.

Miljövariabler.

Resursidentifierare (till exempel hantera namn).

Skiftlägeskänsliga säkerhetsrelaterade inställningar.
En icke-språklig identifierare, där fallet är irrelevant. OrdinalIgnoreCase
Vissa beständiga, språkligt relevanta data.

Visning av språkdata som kräver en fast sorteringsordning.
Kulturellt agnostiska data som fortfarande är språkligt relevanta. InvariantCulture

-eller-

InvariantCultureIgnoreCase
Data som visas för användaren.

De flesta användarindata.
Data som kräver lokala språkliga seder. CurrentCulture

-eller-

CurrentCultureIgnoreCase

Vanliga metoder för strängjämförelse i .NET

I följande avsnitt beskrivs de metoder som oftast används för strängjämförelse.

String.Compare

Standardtolkning: StringComparison.CurrentCulture.

Eftersom åtgärden är mest central för strängtolkning bör alla instanser av dessa metodanrop undersökas för att avgöra om strängar ska tolkas enligt den aktuella kulturen eller skiljas från kulturen (symboliskt). Vanligtvis är det det senare, och en StringComparison.Ordinal jämförelse bör användas i stället.

Klassen System.Globalization.CompareInfo , som returneras av CultureInfo.CompareInfo egenskapen, innehåller också en Compare metod som tillhandahåller ett stort antal matchande alternativ (ordningstal, ignorera tomt utrymme, ignorera kana-typ och så vidare) med hjälp av CompareOptions flagguppräkning.

String.CompareTo

Standardtolkning: StringComparison.CurrentCulture.

Den här metoden erbjuder för närvarande inte någon överlagring som anger en StringComparison typ. Det är vanligtvis möjligt att konvertera den här metoden till det rekommenderade String.Compare(String, String, StringComparison) formuläret.

Typer som implementerar gränssnitten IComparable och IComparable<T> implementerar den här metoden. Eftersom den inte erbjuder alternativet för en StringComparison parameter låter implementeringstyper ofta användaren ange en StringComparer i sin konstruktor. I följande exempel definieras en FileName klass vars klasskonstruktor innehåller en StringComparer parameter. Det här StringComparer objektet används sedan i FileName.CompareTo -metoden.

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

Standardtolkning: StringComparison.Ordinal.

Med String klassen kan du testa likheten genom att anropa antingen överlagringen av statiska instanser eller instansmetoden Equals eller med hjälp av operatorn för statisk likhet. Överlagringarna och operatorn använder ordningstalsjämförelse som standard. Vi rekommenderar dock fortfarande att du anropar StringComparison en överlagring som uttryckligen anger typen även om du vill göra en ordningsjämförelse. Detta gör det enklare att söka efter kod för en viss strängtolkning.

String.ToUpper och String.ToLower

Standardtolkning: StringComparison.CurrentCulture.

Var försiktig när du använder String.ToUpper() metoderna och String.ToLower() eftersom tvinga en sträng till versaler eller gemener ofta används som en liten normalisering för att jämföra strängar oavsett skiftläge. I så fall bör du överväga att använda en skiftlägeskänslig jämförelse.

Metoderna String.ToUpperInvariant och String.ToLowerInvariant är också tillgängliga. ToUpperInvariant är standardsättet för att normalisera skiftläge. Jämförelser som görs med hjälp StringComparison.OrdinalIgnoreCase av är beteendemässigt sammansättningen av två anrop: anropar ToUpperInvariant båda strängargumenten och gör en jämförelse med .StringComparison.Ordinal

Överlagringar är också tillgängliga för konvertering till versaler och gemener i en viss kultur, genom att skicka ett CultureInfo objekt som representerar den kulturen till metoden.

Char.ToUpper och Char.ToLower

Standardtolkning: StringComparison.CurrentCulture.

Metoderna Char.ToUpper(Char) och Char.ToLower(Char) fungerar på samma sätt som metoderna String.ToUpper() och String.ToLower() som beskrivs i föregående avsnitt.

String.StartsWith och String.EndsWith

Standardtolkning: StringComparison.CurrentCulture.

Som standard utför båda dessa metoder en kulturkänslig jämförelse. I synnerhet kan de ignorera tecken som inte skrivs ut.

String.IndexOf och String.LastIndexOf

Standardtolkning: StringComparison.CurrentCulture.

Det finns en brist på konsekvens i hur standardöverlagringarna av dessa metoder utför jämförelser. Alla String.IndexOf metoder och String.LastIndexOf metoder som innehåller en Char parameter utför en ordningstalsjämförelse, men standardmetoderna String.IndexOf och String.LastIndexOf metoderna som innehåller en String parameter utför en kulturkänslig jämförelse.

Om du anropar String.IndexOf(String) metoden eller String.LastIndexOf(String) och skickar den en sträng för att hitta den aktuella instansen rekommenderar vi att du anropar StringComparison en överlagring som uttryckligen anger typen. De överlagringar som innehåller ett Char argument tillåter inte att du anger en StringComparison typ.

Metoder som utför strängjämförelse indirekt

Vissa icke-strängmetoder som har strängjämförelse som en central åtgärd använder typen StringComparer . Klassen StringComparer innehåller sex statiska egenskaper som returnerar StringComparer instanser vars StringComparer.Compare metoder utför följande typer av strängjämförelser:

Array.Sort och Array.BinarySearch

Standardtolkning: StringComparison.CurrentCulture.

När du lagrar data i en samling eller läser bevarade data från en fil eller databas i en samling kan växling av den aktuella kulturen ogiltigförklara invarianterna i samlingen. Metoden Array.BinarySearch förutsätter att elementen i matrisen som ska sökas igenom redan är sorterade. Om du vill sortera ett strängelement i matrisen Array.Sort anropar String.Compare metoden metoden för att sortera enskilda element. Det kan vara farligt att använda en kulturkänslig jämförelse om kulturen ändras mellan den tid då matrisen sorteras och dess innehåll genomsöks. I följande kod fungerar till exempel lagring och hämtning på jämförelsen som tillhandahålls implicit av Thread.CurrentThread.CurrentCulture egenskapen. Om kulturen kan ändras mellan anropen till StoreNames och DoesNameExist, och särskilt om matrisinnehållet sparas någonstans mellan de två metodanropen, kan binärsökningen misslyckas.

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

En rekommenderad variant visas i följande exempel, som använder samma ordningstal (kulturokänsliga) jämförelsemetod både för att sortera och söka i matrisen. Ändringskoden återspeglas i raderna som är märkta Line A och Line B i de två exemplen.

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

Om dessa data sparas och flyttas mellan kulturer, och sortering används för att presentera dessa data för användaren, kan du överväga att använda StringComparison.InvariantCulture, som fungerar språkligt för bättre användarutdata men inte påverkas av ändringar i kulturen. I följande exempel ändras de två tidigare exemplen för att använda den invarianta kulturen för att sortera och söka i matrisen.

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

Exempel på samlingar: Hashtable-konstruktor

Hashningssträngar är ett andra exempel på en åtgärd som påverkas av hur strängar jämförs.

I följande exempel instansierar ett Hashtable objekt genom att skicka det StringComparer objektet som returneras av StringComparer.OrdinalIgnoreCase egenskapen. Eftersom en klass StringComparer som härleds från StringComparer implementerar IEqualityComparer gränssnittet används dess GetHashCode metod för att beräkna hash-koden för strängar i hash-tabellen.

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

Se även