Sdílet prostřednictvím


Doporučené postupy pro použití řetězců v rozhraní .NET Framework

Rozhraní .NET Framework poskytuje rozsáhlou podporu pro vývoj lokalizovaných a globálních aplikací a usnadňuje aplikování konvencí pro aktuální jazykovou verzi nebo konkrétní jazykovou verzi při provádění běžných operací, jako je řazení a zobrazování řetězců. Řazení nebo porovnávání řetězců ale vždy není operací citlivou na jazykovou verzi. Například řetězce, které jsou používány uvnitř aplikace by obvykle měly být zpracovány identicky pro všechny jazykové verze. Pokud jsou jazykově nezávislá řetězcová data, jako jsou například značky jazyka XML, značky jazyka HTML, uživatelská jména, cesty k souborům a názvy systémových objektů interpretována, jako kdyby byly citlivé na jazykové verze, může být kód aplikace příčinou choulostivých chyb, sníženého výkonu a v některých případech problémů se zabezpečením.

Toto téma zkoumá řazení řetězců, porovnávání a metody pro změnu velikosti písmen v rozhraní .NET Framework verze 4 a novějších, nabízí doporučení pro volbu vhodné metody zpracování řetězců a poskytuje další informace o metodách zpracování řetězců.

Toto téma obsahuje následující oddíly:

  • Doporučení pro používání řetězců

  • Určení explicitního porovnávání řetězců

  • Podrobnosti o porovnávání řetězců

  • Výběr člena StringComparison pro vaše volání metody

  • Společné metody porovnávání řetězců v rozhraní .NET Framework

  • Metody, které provádějí nepřímo porovnávání řetězců

Doporučení pro používání řetězců

Při vývoji v rozhraní .NET Framework postupujte podle následujících jednoduchých doporučení pro používání řetězců:

Při použití řetězců se vyhněte následujícím postupům:

  • Nepoužívejte přetížení, které explicitně nebo implicitně neurčují pravidla pro porovnávání řetězců pro řetězcové operace.

  • Nepoužívejte ve většině případů řetězcové operace založené na StringComparison.InvariantCulture. Jednou z mála výjimek je, když uchováváte lingvisticky smysluplná data, která ale nezávisí na jazykové verzi.

  • Nepoužívejte přetížení metod String.Compare nebo CompareTo a testujte vrácenou hodnotu nuly, pro určení, zda jsou dva řetězce stejné.

Zpět na začátek

Určení explicitního porovnávání řetězců

Většina metod pro manipulaci s řetězci v rozhraní .NET Framework je přetížena. Obvykle jedno nebo více přetížení přijmají výchozí nastavení, zatímco ostatní nepříjmají výchozí nastavení a namísto toho definují přesný způsob, kterým jsou řetězce porovnávány a jak je s nimi manipulovávo. Většina metod, které nespoléhají na výchozí hodnoty, obsahují parametr typu StringComparison, což je výčet, který explicitně určuje pravidla pro porovnávání řetězců podle jazykové verze a velikosti písmen. Následující tabulka popisuje členy výčtu StringComparison.

Člen StringComparison

Popis

CurrentCulture

Provádí porovnání s rozlišením velkých a malých písmen s použitím aktuální jazykové verze.

CurrentCultureIgnoreCase

Provádí porovnání bez rozlišení velkých a malých písmen s použitím aktuální jazykové verze.

InvariantCulture

Provádí porovnání s rozlišením velkých a malých písmen s použitím invariantní jazykové verze.

InvariantCultureIgnoreCase

Provádí porovnání bez rozlišování velkých a malých písmen s použitím invariantní jazykové verze.

Ordinal

Provádí ordinální porovnávání.

OrdinalIgnoreCase

Provádí ordinální porovnání bez rozlišení velkých a malých písmen.

Například metoda IndexOf, která vrátí index podřetězce v objektu String, který odpovídá znaku nebo řetězci, obsahuje devět přetížení:

Doporučujeme, abyste vybrali přetížení, které nepoužívá výchozí hodnoty z následujících důvodů:

  • Některé přetížení s výchozími parametry (ty, které vyhledávají Char v instanci řetězce) provádějí ordinálni porovnávaní, zatímco ostatní (ty, které vyhledávají řetězec v instanci řetězce) jsou citlivé na jazykovou verzi. Je obtížné pamatovat si, která metoda používá danou výchozí hodnotu a je snadné zaměnit přetížení.

  • Záměr kódu, který je založen na výchozích hodnotách pro volání metoda není zřejmý. V následujícím příkladu, který spoléhá na výchozí hodnoty, je obtížné zjistit, zda vývojář skutečně chtěl použít ordinální nebo lingvistické porovnání dvou řetězců, nebo zda rozdíl velikosti písmen mezi protocol a http mohl způsobit, aby test rovnosti vrátil false.

    Dim protocol As String = GetProtocol(url)       
    If String.Equals(protocol, "http") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    
    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

Obecně doporučujeme, abyste volali metodu, která nespoléhá na výchozí hodnoty, protože toto způsobuje, že záměr kódu je nejednoznačný. Toto naopak dělá kód čitelnější a umožňuje snadnější ladění a údržbu. Následující příklad je ekvivalentní předchozímu kódu, s výjimkou toho, že je jasné, že je použito ordinální porovnávání, a že jsou ignorovány rozdíly ve velikosti písmen.

Dim protocol As String = GetProtocol(url)       
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
   ' ...Code to handle HTTP protocol.
Else
   Throw New InvalidOperationException()
End If   
string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}

Zpět na začátek

Podrobnosti o porovnávání řetězců

Porovnávání řetězců je srdcem mnoha operací týkajících se řetězců, zejména řazení a testování rovnosti. Řetězce jsou řazeny v určeném pořadí: Pokud se řetězec my zobrazuje před řetězcem string v seřazeném seznamu řetězců, řetězec my musí být výše než řetězec string. Kromě toho porovnávání implicitně definuje rovnost. Operace porovnávání vrátí hodnotu 0 pro řetězce, které považuje za rovné. Správným výkladem je, že žádný řetězec není menší než jiný. Většina smysluplných operací zahrnujících řetězce obsahuje jednu nebo obě tyto procedury: porovnávání s jiným řetězcem a provádění dobře definovaných operací řazení.

Avšak porovnávání rovnosti dvou řetězců nebo pořadí setřízení nemá jediný správný výsledek. Výsledek závisí na kritériích používaných pro porovnávání řetězců. Zejména porovnávání řetězců, které jsou ordinální, nebo které jsou založeny na konvencích pro řazení a rozlišování velkých a malých písmen pro aktuální jazykovou verzi nebo invariantní jazykovou verzi (jazyková verze agnostic založená na anglickém jazyce), mohou mít různé výsledky.

Porovnávání řetězců, které používají aktuální jazykovou verzi

Jedno kritérium zahrnuje používání konvencí aktuální jazykové verze při porovnávání řetězců. Porovnávání, která jsou založena na aktuální jazykové verzi používají aktuální jazykovou verzi vlákna nebo místní jazykovou verzi. Pokud není jazyková verze nastavena uživatelem, je standardně nastavena na hodnotu uvedenou v okně Místní nastavení v Ovládacích panelech. Vždy byste měli použít porovnávání založené na aktuální jazykové verzi, když jsou data lingvisticky relevantní a v případě, že odráží uživatelskou interakce, která bere ohled na jazykovou verzi.

Avšak chování porovnávání a velikosti písmen v rozhraní .NET Framework se mění při změně jazykové verze. K tomu dojde, pokud je aplikace spuštěna v počítači, který má jinou jazykovou verzi než počítač, na kterém byla aplikace vyvinuta nebo při změně jazykové verze spuštěného vlákna. Toto chování je úmyslné, ale mnoho vývojářům není zřejmé. Následující příklad ukazuje rozdíly v pořadí řazení v jazykových verzích U.S. English ("en-US") a Swedish ("sv-SE"). Všimněte si, že se slova ångström, Windows a "Visual Studio" zobrazují na různých pozicích v seřazených polích řetězců.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim values() As String = { "able", "ångström", "apple", _
                                 "Æble", "Windows", "Visual Studio" }
      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

    Private Sub DisplayArray(values() As String)
      Console.WRiteLine("Sorting using the {0} culture:", _ 
                        CultureInfo.CurrentCulture.Name)
      For Each value As String In values
         Console.WriteLine("   {0}", 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
'          Æble
'          apple
'          Windows
'          Visual Studio
'          ångström
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string[] values= { "able", "ångström", "apple", "Æble", 
                         "Windows", "Visual Studio" };
      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);
    }

    private static void DisplayArray(string[] values)
    {
      Console.WriteLine("Sorting using the {0} culture:",  
                        CultureInfo.CurrentCulture.Name);
      foreach (string value in values)
         Console.WriteLine("   {0}", 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
//          Æble
//          apple
//          Windows
//          Visual Studio
//          ångström

Porovnávání bez rozlišování velkých a malých písmen používající aktuální jazykovou verzi jsou stejné jako porovnávání, které zohledňují jazykové verze, s tím rozdílem, že ignorují velikost písmen, jak je dáno jazykovou verzí vlákna. Toto chování může také vést k setřízenému chování.

Porovnání používající sémantiku aktuální jazykové verze jsou výchozí pro následující metody:

V každém případě doporučujeme volat přetížení, které má parametr StringComparison, aby byl jasný záměr volání metody.

Mohou se objevit více a méně choulostivé chyby, když jsou nelingvistická data interpretována lingvisticky nebo když jsou řetězcová data z konkrétní jazykové verze interpretována za použití konvencí jiné jazykové verze. Kanonický příklad je problém s tureckým písmenem I.

Pro téměř všechny abacedy v latince, včetně U.S. English, znak i (\u0069) je malým písmenem znaku I (\u0049). Toto pravidlo velikosti se pro některé stalo při programování v takovéto jazykové verzi výchozím. Avšak turecká abeceda (tr-TR) obsahuje znak I s tečkou, znak İ (\u0130), což je velká verze od znaku i. Turečtina také obsahuje malý znak písmena i bez tečky, ı (\u0131), jehož velká verze je I. K tomuto chování dochází také u jazykové verze Azerština (az).

Proto předpoklady o převodu písmena i na velké písmeno nebo předpoklady o převodu písmena I na malé písmeno nejsou platné pro všechny jazykové verze. Používáte-li výchozí přetížení rutin pro porovnávání řetězců, budou se lišit v jednotlivých jazykových verzích. Jsou-li data, která mají být porovnávána lingvistická, použití výchozích přetížení může způsobit nežádoucí výsledky, jako ukazuje následující pokus o provedení porovnání bez rozlišování velkých a malých písmen řetězce file a FILE.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim fileUrl = "file"
      Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                       fileUrl.StartsWith("FILE", True, Nothing))
      Console.WriteLine()

      Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                        fileUrl.StartsWith("FILE", True, Nothing))
   End Sub
End Module
' The example displays the following output:
'       Culture = English (United States)
'       (file == FILE) = True
'       
'       Culture = Turkish (Turkey)
'       (file == FILE) = False
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string fileUrl = "file";
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                       fileUrl.StartsWith("FILE", true, null));
      Console.WriteLine();

      Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                        fileUrl.StartsWith("FILE", true, null));
   }
}
// The example displays the following output:
//       Culture = English (United States)
//       (file == FILE) = True
//       
//       Culture = Turkish (Turkey)
//       (file == FILE) = False

Toto porovnání může způsobit významné problémy, pokud je jazyková verze neúmyslně používána v nastaveních citlivých na bezpečnost, jako v následujícím příkladu. Volání metody jako například IsFileURI("file:") vrátí true pokud je aktuální jazyková verze U.S. English, ale false pokud je aktuální jazyková verze Turkish. V tureckých systémech by tedy někdo mohl obejít bezpečnostní opatření, která blokují přístup k adresám URI, které neborou ohled na velká a malá písmena, které začínají řetězcem FILE:.

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

V tomto případě protože "soubor:" je určena interpretovat jako identifikátor-jazykové a necitlivý kultury, kód by místo zapsány jako v následujícím příkladu.

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

Avšak předchozí příklad používá pro otestování rovnosti metodu String.StartsWith(String, StringComparison). Vzhledem k tomu, že účelem porovnání je otestovat rovnost místo řazení řetězců, lepší alternativou je volat metodu Equals, jak je znázorněno v následujícím příkladu.

Public Shared Function IsFileURI(path As String) As Boolean
   If (path.Length < 5) Then Return False

   Return String.Equals(path.Substring(0, 5), "FILE:", _ 
                        StringComparison.OrdinalIgnoreCase)
End Function   
public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                        StringComparison.OrdinalIgnoreCase);
}   

Ordinální operace s řetězci

Určení hodnoty StringComparison.Ordinal nebo StringComparison.OrdinalIgnoreCase ve volání metody znamená nelingvistické porovnání, ve kterém jsou ignorovány vlastnosti přirozeného jazyka. Metody, které jsou spuštěny s těmito hodnotami StringComparison zákládají rozhodování řetězcových operací na jednoduchém porovnání bytů namísto porovnávání pomocí tabulek, které určují příslušné shodné znaky a znaky, které si odpovídají podle velikosti, které jsou závislé na aktuální jazykové verzi. Ve většině případů tento přístup nejlépe odpovídá zamýšlenému výkladu řetězce a navíc umožňuje rychlejší provádění kódu a lepší čitelnost.

Ordinální porovnávání jsou porovnávání řetězců, při kterých je každý byte řetězce porovnán bez lingvistické interpretace. Například windows neodpovídá Windows. Toto je v podstatě volání funkce strcmp modulu runtime C. Toto porovnání použijte v případě, že kontext určuje, že by se řetězce měly shodovat přesně nebo v případě konzervativních požadavků na porovnávání. Ordinální porovnávání je navíc nejrychlejší porovnávání, protože nepoužívá žádná lingvistická pravidla při určování výsledku.

Řetězce v rozhraní .NET Framework mohou obsahovat vložené znaky null. Jeden z nejjasnějších rozdílů mezi ordinálním porovnáváním a porovnáváním, které bere ohled na jazykovou verzi (včetně porovnávání používajících invariantní jazykovou verzi) se týká zpracování vložených znaků null v řetězci. Tyto znaky jsou ignorovány, když použijete metody String.Compare a String.Equals pro provedení porovnání, které zohledňuje jazykovou verzi (včetně porovnání používajících invariantní jazykovou verzi). V důsledku toho v porovnávání, které zohledňuje jazykové verze, řetězce, které obsahují vložené znaky null lze považovat za shodné s řetězci, které je neobsahují.

Důležitá poznámkaDůležité

Přestože metody pro porovnávání řetězců nezohledňují vložené znaky null, metody pro vyhledávání řetězců, jako například String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf a String.StartsWith fungují opačně.

Následující příklad provádí porovnání, které zohledňuje jazykovou verzi pro řetězec Aa a podobný řetězec, který obsahuje několik vložených znaků null mezi A a a a ukazuje, jak jsou tyto dva řetězce považovány za shodné.

Module Example
   Public Sub Main()
      Dim str1 As String = "Aa"
      Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                        str1, ShowBytes(str1), str2, ShowBytes(str2))
      Console.WriteLine("   With String.Compare:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.InvariantCulture))

      Console.WriteLine("   With String.Equals:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.InvariantCulture))
   End Sub

   Private 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 = String.Empty
         result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
         hexString += result
      Next
      Return hexString.Trim()
   End Function
End Module
using System;

public class Example
{
   public static void Main()
   {
      string str1 = "Aa";
      string str2 = "A" + new String('\u0000', 3) + "a";
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                        str1, ShowBytes(str1), str2, ShowBytes(str2));
      Console.WriteLine("   With String.Compare:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.InvariantCulture));

      Console.WriteLine("   With String.Equals:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.InvariantCulture));
   }

   private static string ShowBytes(string str)
   {
      string hexString = String.Empty;
      for (int ctr = 0; ctr < str.Length; ctr++)
      {
         string result = String.Empty;
         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:
//          Current Culture: 0
//          Invariant Culture: 0
//       With String.Equals:
//          Current Culture: True
//          Invariant Culture: True

Avšak řetězce nejsou považovány za shodné při použití ordinálního porovnání jak je ukázáno v následujícím příkladu.

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                  str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine("   With String.Compare:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine("   With String.Equals:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Equals(str1, str2, StringComparison.Ordinal))
' 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
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                  str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine("   With String.Compare:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine("   With String.Equals:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Equals(str1, str2, StringComparison.Ordinal));
// 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

Ordinální porovnávání bez rozlišení velkých a malých písmen je nejvíce konzervativním přístupem. Toto porovnávání ignoruje většinu velikosti písmen, například tedy windows odpovídá Windows. Při zpracovávání ASCII znaků jsou tyto zásady ekvivalentní k StringComparison.Ordinal, s tím rozdílem, že ignorují obvyklé velikosti ASCII znaků. Proto libovolný znak v [A, Z] (\u0041-\u005A) odpovídá příslušnému znaku v [a, z] (\u0061-\007A). Velikost znaků mimo rozsah ASCII používá invariantní tabulky jazykové verze. Proto následující porovnání:

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

je rovnocenné (ale rychlejší než) toto porovnávání:

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

Tato porovnávání jsou stále velice rychlé.

PoznámkaPoznámka

Toto chování řetězců souborového systému, klíčů a hodnot registru, a proměnných prostředí je nejlépe představováno StringComparison.OrdinalIgnoreCase.

Obě hodnoty StringComparison.Ordinal a StringComparison.OrdinalIgnoreCase používají přímo binární hodnoty a hodí se nejlépe pro porovnávání. Když si nejste jisti s nastavením porovnávání řetězců, použijte jednu z těchto dvou hodnot. Avšak protože provádějí porovnávání byte po bytu, neposkytují řazení podle jazykových pravidel (jako například anglický slovník), ale poskytují binární řazení. Výsledky mohou vypadat nevhodně ve většině případů, když jsou zobrazeny uživatelům.

Ordinální sémantika je výchozí pro přetížení String.Equals, které neobsahují argument StringComparison (včetně operátoru rovnosti). V každém případě doporučujeme volat přetížení, které má parametr StringComparison.

Řetězcové operace, které používají invariantní jazykovou verzi

Porovnávání s invariantní jazykovou verzí používá vlastnost CompareInfo vrácenou statickou vlastností CultureInfo.InvariantCulture. Toto chování je stejné ve všech systémech. Přeloží jakékoli znaky mimo daný rozsah na znaky, které považuje za odpovídající invariantní znaky. Tato zásada může být užitečná pro uchování jedné sady řetězcového chování napříč jazykovými verzemi, ale často poskytuje neočekávané výsledky.

Porovnávání bez ohledu na velká a malá s invariantní jazykovou verzí používá také statickou vlastnost CompareInfo vrácenou statickou vlastností CultureInfo.InvariantCulture pro informace o porovnávání. Jakýkoli rozdíl ve velikosti písmen mezi těmito přeloženými znaky je ignorován.

Porovnání, které používají StringComparison.InvariantCulture a StringComparison.Ordinal pracují stejně s řetězci standardu ASCII. Avšak StringComparison.InvariantCulture způsobuje výskyt lingvistických rozhodnutí, která nemusí být vhodné pro řetězce, které musí být vykládány jako sada bajtů. Objekt CultureInfo.InvariantCulture.CompareInfo způsobuje, že metoda Compare interpretuje určité sady znaků jako ekvivalentní. Například následující ekvivalence je platná v invariantní jazykové verzi:

InvariantCulture: + ̊ =

Malé písmeno LATINKY A znak "a" (\u0061), pokud je vedle znak Diakritické znaménko KROUŽEK nad "+"̊" (\u030a) je interpretován jako LATINKY malé písmeno A S KROUŽKEM nad znakem "a" (\u00e5). Jak ukazuje následující příklad, toto chování se liší od ordinálního porovnávání.

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

Při interpretaci názvů souborů, souborů cookie nebo čehokoli jiného, kde se může objevit kombinace jako například å, ordinální porovnání stále nabízí nejtransparentnější a nejvhodnější chování.

Při bližší úvaze má invariantní jazyková verze velmi málo vlastností, které jsou užitečné pro porovnávání. Provede porovnání lingvisticky relevantním způsobem, který zabraňuje zaručení plné ekvivalence souborů, ale to není volbou pro zobrazení v libovolné jazykové verzi. Jeden z mála důvodů pro použití StringComparison.InvariantCulture pro porovnání je zachování seřazených dat pro stejné zobrazení ve všech jazykových verzích. Například pokud velký datový soubor obsahující seřazený seznam identifikátorů pro zobrazení v aplikaci, přidání do tohoto seznamu by vyžadovalo vložení pomocí invariantního stylu řazení.

Zpět na začátek

Výběr člena StringComparison pro vaše volání metody

Následující tabulka popisuje mapování ze sémantického kontextu na výčet členů StringComparison.

Data

Chování

Odpovídající System.StringComparison

hodnota

Interní identifikátory s rozlišováním velkých a malých písmen.

Identifikátory rozlišující velká a malá písmena ve standardech jako například jazyk XML a protokol HTTP.

Bezpečnostní nastavení související s rozlišováním velkých a malých písmen.

Nelingvistický identifikátor, kde si bajty přesně odpovídají.

Ordinal

Interní identifikátory bez rozlišování velkých a malých písmen.

Identifikátory nerozlišující velká a malá písmena ve standardech jako například jazyk XML a protokol HTTP.

Cesty k souborům.

Klíče a hodnoty registru.

Proměnné prostředí.

Identifikátory prostředků (například názvy popisovačů).

Bezpečnostní nastavení související s nerozlišováním velkých a malých písmen.

Nelingvistický identifikátor, kde je velikost písmen irelevantní, zvláště data uložená ve většině systémových služeb systému Windows.

OrdinalIgnoreCase

Některé trvalé, lingvisticky relevantní data.

Zobrazení lingvistických dat, která požadují pevné pořadí třídění.

Kulturně agnostická data, která jsou stále lingvisticky relevantní.

InvariantCulture

-nebo-

InvariantCultureIgnoreCase

Data zobrazená uživateli.

Většina vstupu uživatele.

Data, která vyžaduje místní lingvistické zásady.

CurrentCulture

-nebo-

CurrentCultureIgnoreCase

Zpět na začátek

Společné metody porovnávání řetězců v rozhraní .NET Framework

V následujících částech jsou popsány metody, které se nejčastěji používají pro porovnávání řetězců.

String.Compare

Výchozí výklad: StringComparison.CurrentCulture.

Vzhledem k tomu, že operace se nejvíce zaměřují na interpretaci řetězce, všechny instance těchto volání metod by měly být zkontrolovány pro zjištění, zda by řetězce měly být interpretovány podle aktuální jazykové verze, nebo by měly být odděleny od jazykové verze (symbolicky). Obvykle je to ten starší a místo něj by mělo být použito porovnávání StringComparison.Ordinal.

Třída System.Globalization.CompareInfo, která je vrácena vlastností CultureInfo.CompareInfo takéobsahuje metodu, Compare, která poskytuje velké množství odpovídajících možností (ordinální, ignorace prázdných znaků, ignorace typu kana, atd) prostřednictvím příznaku výčtu CompareOptions.

String.CompareTo

Výchozí výklad: StringComparison.CurrentCulture.

Tato metoda aktuálně nenabízí přetížení, které určuje typ StringComparison. Obvykle je možné převést tyto metody na doporučenou formu String.Compare(String, String, StringComparison).

Typy, které implementují rozhraní IComparable a IComparable<T> implementují tuto metodu. Protože nenabízí možnost parametru StringComparison, typy implementace často umožní uživateli zadání StringComparer v jejím konstruktoru. Následující příklad definuje třídu FileName jejichž konstruktor třídy zahrnuje parametr StringComparer. Tento objekt StringComparer se pak používá v metodě FileName.CompareTo.

Public Class FileName : Implements IComparable
   Dim fname As String
   Dim comparer As StringComparer 

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

      Me.fname = name

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

   Public ReadOnly Property Name As String
      Get
         Return fname
      End Get   
   End Property

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

      If Not TypeOf obj Is FileName Then
         obj = obj.ToString()
      Else
         obj = CType(obj, FileName).Name
      End If         
      Return comparer.Compare(Me.fname, obj)
   End Function
End Class
using System;

public class FileName : IComparable
{
   string fname;
   StringComparer comparer; 

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

      this.fname = name;

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

   public string Name
   {
      get { return fname; }
   }

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

      if (! (obj is FileName))
         return comparer.Compare(this.fname, obj.ToString());
      else
         return comparer.Compare(this.fname, ((FileName) obj).Name);
   }
}

String.Equals

Výchozí výklad: StringComparison.Ordinal.

Třída String umožňuje testovat rovnost voláním buď statického nebo instančního přetížení metody Equals, nebo pomocí statického operátoru rovnosti. Přetížení a operátor používají ve výchozím nastavení ordinální porovnávání. Přesto však doporučujeme volat přetížení, které explicitně určuje typ StringComparison i v případě, že chcete provést ordinální porovnávání. Tím je usnadněno vyhledávání určitých interpretací řetězce v kódu.

String.ToUpper a String.ToLower

Výchozí výklad: StringComparison.CurrentCulture.

Měli byste být opatrní při používání těchto metod, protože vynucení změny znaků v řetězci na velká nebo malá písmena je často používáno jako malá normalizace pro porovnávání řetězců bez ohledu na velikost písmen. Pokud ano, zvažte použití porovnávání bez rozlišení velkých a malých písmen.

Jsou také dostupné metody String.ToUpperInvariant a String.ToLowerInvariant. ToUpperInvariant je standardním způsobem pro normalizování velikosti písmen. Porovnání pomocí StringComparison.OrdinalIgnoreCase jsou složením dvou volání: volání ToUpperInvariant pro oba řetězcové argumenty a provedení porovnání s použitím StringComparison.Ordinal.

Přetížení jsou také k dispozici pro převod na velká a malá písmena v konkrétní jazykové verzi předáním objektu CultureInfo, který představuje danou jazykovou verzi metodě.

Char.ToUpper a Char.ToLower

Výchozí výklad: StringComparison.CurrentCulture.

Tyto metody fungují podobně jako metody String.ToUpper a String.ToLower popsané v předchozím oddílu.

String.StartsWith a String.EndsWith

Výchozí výklad: StringComparison.CurrentCulture.

Ve výchozím nastavení obě tyto metody provádějí porovnávání s ohledem na jazykovou verzi.

String.IndexOf a String.LastIndexOf

Výchozí výklad: StringComparison.CurrentCulture.

Je zde nedostatek konzistence v tom, jak výchozí přetížení těchto metod provádí porovnávání. Všechny metody String.IndexOf a String.LastIndexOf, které obsahují parametr Char provádějí ordinální porovnávání, ale výchozí metody String.IndexOf a String.LastIndexOf, které obsahují parametr String provádějí porovnávání s ohledem na jazykové verze.

Pokud zavoláte metodu String.IndexOf(String) nebo String.LastIndexOf(String) a předáte ji řetězec pro vyhledání v aktuální jazykové verzi, doporučujeme volat přetížení určující explicitně typ StringComparison. Přetížení, které obsahují argument Char neumožňují zadat typ StringComparison.

Zpět na začátek

Metody, které provádějí nepřímo porovnávání řetězců

Některé neřetězcové metody, které mají porovnávání řetězců jako centrální operaci používají typ StringComparer. Třída StringComparer zahrnuje šest statických vlastností, které vracejí instance StringComparer, jejíchž metody StringComparer.Compare provádějí následující typy porovnávání řetězců:

Array.Sort a Array.BinarySearch

Výchozí výklad: StringComparison.CurrentCulture.

Když uložíte jakékoli data v kolekci nebo čtete trvalá data te souboru nebo databáze do kolekce, změna aktuální jazykové verze může znehodnotit invarianty v kolekci. Metoda Array.BinarySearch předpokládá, že prvky pole, které mají být prohledány, jsou již seřazeny. Chcete-li seřadit libovolný prvek řetězce v poli, metoda Array.Sort volá metodu String.Compare pro seřazení jednotlivých prvků. Použití porovnávání citlivého na jazykovou verzi může být nebezpečné, pokud se jazyková verze změní v době mezi tím, kdy je pole seřazeno a mezi tím, kdy je prohledáno. Například v následujícím kódu probíhá uložení a načtení při porovnávání poskytovaném implicitně vlastností Thread.CurrentThread.CurrentCulture. Pokud se jazyková verze může změnit mezi voláním StoreNames a DoesNameExist a zejména v případě, že je obsah pole trvalý někde mezi dvěmi voláními metod, binární vyhledávání se pravděpodobně nezdaří.

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names)          ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name) >= 0      ' Line B.
End Function
// Incorrect.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

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

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name) >= 0);  // Line B.
}

Doporučená odchylka se zobrazí v následujícím příkladu, který používá stejné ordinální porovnávání (bez ohledu na jazykovou verzi) pro řazení i prohledávání pole. Změna kódu je označena na řádcích označených Line A a Line B v obou příkladech.

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

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

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

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

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0);  // Line B.
}

Pokud jsou tato data trvalá a přesunuta mezi jazykovými verzemi a řazení je použito pro zobrazení těchto dat uživateli, můžete zvážit použití StringComparison.InvariantCulture, která funguje lingvisticky pro lepší uživatelský výstup, ale není ovlivněna změnami v jazykové verzi. Následující příklad upravuje dva předchozí příklady pro použití invariantní jazykové verze pro řazení a prohledávání pole.

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

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

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

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

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0);  // Line B.
}

Příklad kolekcí: Konstruktor Hashtable

Řetězce hash poskytují druhý příklad operace, která je ovlivněna způsobem porovnání řetězců.

Následující příklad vytvoří instanci objektu Hashtable předáním objektu StringComparer, který je vrácený vlastností StringComparer.OrdinalIgnoreCase. Protože třída StringComparer, která je odvozena z StringComparer implementuje rozhraní IEqualityComparer, jeho metoda GetHashCode slouží k výpočtu hash hodnoty řetězců v tabulce hash hodnot.

Const initialTableCapacity As Integer = 100
Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)
   h = New Hashtable(initialTableCapacity, _
                     StringComparer.OrdinalIgnoreCase)

   For Each filename As String In Directory.GetFiles(dir)
      h.Add(filename, File.GetCreationTime(filename))
   Next                        
End Sub

Public Sub PrintCreationTime(targetFile As String)
   Dim dt As Object = h(targetFile)
   If dt IsNot Nothing Then
      Console.WriteLine("File {0} was created at {1}.", _
         targetFile, _
         CDate(dt))
   Else
      Console.WriteLine("File {0} does not exist.", targetFile)
   End If
End Sub  
const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)
{
   h = new Hashtable(initialTableCapacity, 
                     StringComparer.OrdinalIgnoreCase);

   foreach (string file in Directory.GetFiles(directory))
         h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)
{
   Object dt = h[targetFile];
   if (dt != null)
   {
      Console.WriteLine("File {0} was created at time {1}.",
         targetFile, 
         (DateTime) dt);
   }
   else
   {
      Console.WriteLine("File {0} does not exist.", targetFile);
   }
}

Zpět na začátek