Práticas recomendadas para comparar cadeias de caracteres no .NET

O .NET dá um amplo suporte para desenvolvimento de aplicativos localizados e globalizados e torna mais fácil aplicar as convenções da cultura atual ou de uma cultura específica ao executar operações comuns, como a classificação e a exibição cadeias de caracteres. Mas a classificação ou a comparação de cadeias de caracteres nem sempre é uma operação que leva em conta a cultura. Por exemplo, cadeias de caracteres que são usadas internamente por um aplicativo normalmente devem ser manipuladas de maneira idêntica em todas as culturas. Quando os dados de cadeias de caracteres culturalmente independentes, como marcações XML, marcações HTML, nomes de usuário, caminhos de arquivo e nomes de objetos do sistema, são interpretados como se levassem em conta a cultura, o código do aplicativo pode estar sujeito a bugs sutis, desempenho ruim e, em alguns casos, problemas de segurança.

Este artigo examina os métodos de classificação, comparação e o uso de maiúsculas e minúsculas de cadeias de caracteres no .NET, apresenta recomendações para a seleção de um método de manipulação de cadeia de caracteres adequado e fornece informações adicionais sobre os métodos de manipulação de cadeia de caracteres.

Recomendações para uso da cadeia de caracteres

Ao desenvolver com o .NET, siga estas recomendações simples ao comparar cadeias de caracteres:

Evite as seguintes práticas ao comparar cadeias de caracteres:

  • Não use sobrecargas que não especificam explicita ou implicitamente as regras de comparação de cadeias de caracteres para operações de cadeia de caracteres.
  • Não use operações de cadeia de caracteres com base em StringComparison.InvariantCulture na maioria dos casos. Uma das poucas exceções é quando você persiste dados de forma linguisticamente significativa, mas independente de cultura.
  • Não use uma sobrecarga dos métodos String.Compare ou CompareTo e teste para um valor retornado de zero para determinar se duas cadeias de caracteres são iguais.

Especificação explícita de comparações da cadeia de caracteres

A maioria dos métodos de manipulação de cadeia de caracteres no .NET é sobrecarregada. Normalmente, uma ou mais sobrecargas aceitam as configurações padrão, enquanto outras não aceitam nenhum padrão e definem a maneira exata em que as cadeias de caracteres devem ser comparadas ou manipuladas. A maioria dos métodos que não dependem de padrões inclui um parâmetro do tipo StringComparison, que é uma enumeração que especifica explicitamente as regras de comparação de cadeia de caracteres por cultura e maiúsculas e minúsculas. A tabela a seguir descreve os membros de enumeração StringComparison.

Membro de StringComparison Descrição
CurrentCulture Executa uma comparação que diferencia maiúsculas de minúsculas usando a cultura atual.
CurrentCultureIgnoreCase Executa uma comparação que não diferencia maiúsculas de minúsculas usando a cultura atual.
InvariantCulture Executa uma comparação que diferencia maiúsculas de minúsculas usando a cultura invariável.
InvariantCultureIgnoreCase Executa uma comparação que não diferencia maiúsculas de minúsculas usando a cultura invariável.
Ordinal Executa uma comparação ordinal.
OrdinalIgnoreCase Executa uma comparação ordinal que não diferencia maiúsculas de minúsculas.

Por exemplo, o método IndexOf, que retorna um índice de uma subcadeia de caracteres em um objeto String que corresponde a um caractere ou cadeia de caracteres, tem nove sobrecargas:

É recomendável que você selecione uma sobrecarga que não usa valores padrão, pelos seguintes motivos:

  • Algumas sobrecargas com parâmetros padrão (aqueles que pesquisam um Char na instância de cadeia de caracteres) realizam uma comparação ordinal, enquanto outras (aquelas que pesquisam uma cadeia de caracteres na instância de cadeia de caracteres) levam em consideração a cultura. É difícil lembrar qual método usa o valor padrão e é fácil confundir as sobrecargas.

  • A intenção do código que depende de valores padrão para chamadas de método não é clara. No exemplo a seguir, que se baseia em padrões, é difícil saber se o desenvolvedor realmente planejava uma comparação ordinal ou linguística de duas cadeias de caracteres ou se uma diferença entre maiúsculas e minúsculas entre protocol e “http” pode fazer com que o teste de igualdade retorne false.

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

Em geral, é recomendável que você chame um método que não se baseie em padrões, pois ele torna a intenção do código clara. Isso, por sua vez, torna o código mais legível e fácil de depurar e manter. O exemplo a seguir aborda as questões levantadas sobre o exemplo anterior. Ele deixa claro que a comparação ordinal é usada e que as diferenças de maiúsculas e minúsculas são ignoradas.

string protocol = GetProtocol(url);
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}
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

Os detalhes da comparação de cadeia de caracteres

A comparação de cadeia de caracteres é o centro de muitas operações relacionadas à cadeia de caracteres, especialmente a classificação e teste de igualdade. As cadeias de caracteres são classificadas em uma determinada ordem: se “my” aparece antes de “string” em uma lista classificada de cadeias de caracteres, “my” deve comparar menos que ou igual a “string”. Além disso, a comparação define implicitamente a igualdade. A operação de comparação retorna zero para cadeias de caracteres que considerar iguais. Uma boa interpretação é que nenhuma cadeia de caracteres é menor que a outra. Operações mais significativas envolvendo cadeias de caracteres incluem um ou ambos destes procedimentos: comparação com outra cadeia de caracteres e execução de uma operação de classificação bem definida.

Observação

Você pode baixar as Tabelas de peso de classificação, um conjunto de arquivos de texto que contêm informações sobre os pesos de caracteres usados em operações de classificação e comparação dos sistemas operacionais Windows, e a Tabela de elemento de ordenação Unicode padrão, a versão mais recente da tabela de peso de classificação para Linux e macOS. A versão específica da tabela de peso de classificação do Linux e macOS depende da versão das bibliotecas de Componentes internacionais para Unicode instaladas no sistema. Para obter informações sobre versões de ICU e as versões Unicode que elas implementam, veja Baixar ICU.

No entanto, avaliar duas cadeias de caracteres quanto à igualdade ou ordem de classificação não gera um único resultado correto, o resultado depende dos critérios usados para comparar as cadeias de caracteres. Em particular, as comparações de cadeia de caracteres ordinais ou baseadas no uso de maiúsculas e minúsculas e convenções classificação da cultura atual ou da cultura invariável (uma cultura independente de localidade com base no idioma inglês) podem gerar resultados diferentes.

Além disso, comparações de cadeia de caracteres usando versões diferentes do .NET ou usando o .NET em diferentes sistemas operacionais ou versões do sistema operacional podem retornar resultados diferentes. Para obter mais informações, veja Cadeias de caracteres e o padrão Unicode.

Comparações de cadeia de caracteres que usam a cultura atual

Um critério envolve o uso das convenções da cultura atual ao comparar cadeias de caracteres. As comparações que se baseiam na cultura atual usam a localidade ou a cultura atual do thread. Se a cultura não for definida pelo usuário, o padrão será a configuração na janela Opções Regionais no Painel de Controle. Você deve sempre usar comparações que se baseiam na cultura atual quando os dados forem linguisticamente relevantes e quando eles refletirem a interação do usuário que leva em conta a cultura.

No entanto, o comportamento da comparação e do uso de maiúsculas e minúsculas no .NET muda quando a cultura muda. Isso acontece quando um aplicativo é executado em um computador que tem uma cultura diferente do computador em que o aplicativo foi desenvolvido ou quando o thread em execução muda sua cultura. Esse comportamento é intencional, mas permanece não óbvio para muitos desenvolvedores. O exemplo a seguir ilustra as diferenças na ordem de classificação entre as culturas do inglês americano ("en-US") e de sueco ("SV-ES"). Observe que as palavras "ångström", "Windows" e "Visual Studio" aparecem em posições diferentes nas matrizes de cadeias de caracteres classificadas.

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

As comparações que não diferenciam maiúsculas de minúsculas que usam a cultura atual são iguais às comparações que levam em conta a cultura, exceto que elas ignoram as maiúsculas e minúsculas conforme determinado pela cultura atual do thread. Esse comportamento pode se manifestar em ordens de classificação também.

As comparações que usam a semântica de cultura atual são o padrão para os seguintes métodos:

Em qualquer caso, é recomendável que você chame uma sobrecarga que tenha um parâmetro StringComparison para deixar a intenção da chamada do método clara.

Bugs sutis e não tão sutis podem surgir quando dados de cadeias de caracteres não linguísticas são interpretados linguisticamente ou quando os dados da cadeia de caracteres de uma cultura específica são interpretados usando as convenções de outra cultura. O exemplo canônico é o problema do I turco.

Para quase todos os alfabetos latinos, incluindo inglês americano, o caractere "i" (\u0069) é a versão em minúsculas do caractere "I" (\u0049). Essa regra de maiúsculas e minúsculas rapidamente se torna o padrão para alguém programando em tal cultura. No entanto, o alfabeto turco ("tr-TR") inclui um caractere "I com um ponto", "İ" (\u0130), que é a versão maiúscula de "i". O turco também inclui um caractere minúsculo "i sem um ponto", "ı" (\u0131), que em maiúscula é “I”. Esse comportamento também ocorre na cultura azerbaijana ("az").

Portanto, as suposições feitas sobre a colocação do "i" em maiúscula ou do "I" em minúscula não são válidas entre todas as culturas. Se você usar as sobrecargas padrão para rotinas de comparação de cadeias de caracteres, elas estarão sujeitas à variação entre culturas. Se os dados a serem comparados são forem linguísticos, o uso das sobrecargas padrão pode gerar resultados indesejáveis, como a tentativa a seguir de realizar uma comparação que não diferencia maiúsculas de minúsculas das cadeias de caracteres “file” e “FILE” ilustra.

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

Essa comparação pode causar problemas significativos se a cultura for usada inadvertidamente nas configurações sensíveis à segurança, como no exemplo a seguir. Uma chamada de método como IsFileURI("file:") retorna true se a cultura atual for o inglês dos EUA, mas false se a cultura atual for turco. Assim, em sistemas turcos, alguém poderia driblar as medidas de segurança que bloqueiam o acesso a URIs que não diferenciam maiúsculas de minúsculas que começam com “FILE:”.

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

Nesse caso, como "file:" deve ser interpretado como um identificador não linguístico, não sensível à cultura, o código deve ser escrito como mostrado no exemplo a seguir:

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

Operações de cadeia de caracteres ordinais

Especificar o valor StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase em uma chamada de método significa uma comparação não linguística em que os recursos de linguagens naturais são ignorados. Os métodos que são invocados com esses valores de StringComparison baseiam as decisões de operação da cadeia de caracteres em comparações de byte simples em vez de no uso de maiúsculas e minúsculas ou tabelas de equivalência que são parametrizadas pela cultura. Na maioria dos casos, essa abordagem se adapta melhor à interpretação pretendida de cadeias de caracteres, enquanto torna o código mais rápido e confiável.

Comparações ordinais são comparações de cadeia de caracteres nas quais cada byte de cada cadeia de caracteres é comparado sem a interpretação linguística, por exemplo, “windows” não corresponde a “Windows”. Isso é basicamente uma chamada para a função strcmp de runtime de C. Use essa comparação quando o contexto determinar que as cadeias de caracteres devem corresponder exatamente ou exigir uma política de correspondência conservadora. Além disso, a comparação ordinal é a operação de comparação mais rápida porque ela não aplica nenhuma regra linguística ao determinar um resultado.

As cadeias de caracteres no .NET podem conter caracteres nulos inseridos. Uma das diferenças mais clara entre a comparação ordinal e a que leva em conta a cultura (incluindo comparações que usam a cultura invariável) diz respeito à manipulação de caracteres nulos inseridos em uma cadeia de caracteres. Esses caracteres são ignorados quando você usa os métodos String.Compare e String.Equals para realizar comparações que levam em conta a cultura (incluindo comparações que usam a cultura invariável). Como resultado, em comparações que levam em conta a cultura, cadeias de caracteres que contêm caracteres nulos inseridos podem ser consideradas iguais a cadeias de caracteres que não contêm.

Importante

Embora os métodos de comparação de cadeia de caracteres ignorem caracteres nulos inseridos, os métodos de pesquisa de cadeia de caracteres, como String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf e String.StartsWith, não o fazem.

O exemplo a seguir executa uma comparação sensível à cultura da cadeia de caracteres "Aa" com uma cadeia de caracteres semelhante que contém vários caracteres nulos inseridos entre "A" e "a" e mostra como as duas cadeias de caracteres são consideradas iguais:

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

No entanto, as cadeias de caracteres não são consideradas iguais quando você usa a comparação ordinal, como mostra o exemplo a seguir:

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

As comparações ordinais que não diferenciam maiúsculas de minúsculas são a próxima abordagem conservadora. Essas comparações ignoram a maioria das maiúsculas e minúsculas, por exemplo, "windows" corresponde a "Windows". Ao lidar com caracteres ASCII, essa política é equivalente a StringComparison.Ordinal, exceto que ela ignora as maiúsculas e minúsculas de ASCII normais. Portanto, qualquer caractere em [A, Z] (\u0041-\u005A) corresponde ao caractere correspondente em [a,z] (\u0061-\007A). As maiúsculas e minúsculas fora do intervalo de ASCII usam as tabelas de cultura invariável. Portanto, a comparação a seguir:

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

é equivalente a (mas mais rápida do que) esta comparação:

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

Essas comparações ainda são muito rápidas.

StringComparison.Ordinal e StringComparison.OrdinalIgnoreCase usam os valores binários diretamente e são mais adequados para correspondência. Quando você não tiver certeza sobre as configurações de comparação, use um destes dois valores. No entanto, como elas executam uma comparação byte por byte, elas não classificam por uma ordem de classificação linguística (como um dicionário de inglês), mas por ordem de classificação binária. Os resultados podem parecer estranhos na maioria dos contextos se exibido aos usuários.

A semântica ordinal é o padrão para sobrecargas de String.Equals que não incluem um argumento StringComparison (incluindo o operador de igualdade). Em qualquer caso, é recomendável que você chame uma sobrecarga que tenha um parâmetro StringComparison.

Operações da cadeia de caracteres que usam a cultura invariável

As comparações com a cultura invariável usam a propriedade CompareInfo retornada pela propriedade estática CultureInfo.InvariantCulture. Esse comportamento é o mesmo em todos os sistemas, ele converte qualquer caractere fora de seu intervalo no que ele acredita que sejam caracteres invariáveis equivalentes. Essa política pode ser útil para manter um conjunto de comportamentos de cadeia de caracteres entre culturas, mas geralmente fornece resultados inesperados.

As comparações que não diferenciam maiúsculas de minúsculas com a cultura invariável usam a propriedade CompareInfo estática retornada pela propriedade CultureInfo.InvariantCulture estática para informações de comparação também. As diferenças de maiúsculas e minúsculas entre esses caracteres convertidos são ignoradas.

As comparações que usam StringComparison.InvariantCulture e StringComparison.Ordinal funcionam de forma idêntica em cadeias de caracteres ASCII. No entanto, StringComparison.InvariantCulture toma decisões linguísticas que podem não ser apropriadas para cadeias de caracteres que devem ser interpretadas como um conjunto de bytes. O objeto CultureInfo.InvariantCulture.CompareInfo faz o método Compare interpretar determinados conjuntos de caracteres como equivalentes. Por exemplo, a equivalência a seguir é válida na cultura invariável:

InvariantCulture: a + ̊ = å

O caractere LATIN SMALL LETTER A "a" (\u0061), quando está ao lado do caractere COMBINING RING ABOVE "+ " " (\u030a), é interpretado como o caractere LATIN SMALL LETTER A WITH RING ABOVE "å" (\u00e5). Como mostra o exemplo a seguir, esse comportamento é diferente da comparação ordinal.

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

Ao interpretar nomes de arquivo, cookies ou qualquer outro elemento em que uma combinação como "å" pode aparecer, as comparações ordinais ainda oferecem o comportamento mais transparente e adequado.

De forma geral, a cultura invariável tem muito poucas propriedades que a tornam útil para comparação. Ela faz a comparação de maneira linguisticamente relevante, o que impede que ela garanta a equivalência simbólica total, mas não é a opção de exibição em nenhuma cultura. Um dos motivos para usar StringComparison.InvariantCulture para comparação é persistir dados ordenados, para uma exibição idêntica entre culturas. Por exemplo, se um arquivo de dados grande que contém uma lista de identificadores classificados para exibição acompanha um aplicativo, a adição a essa lista exige uma inserção com a inserção de estilo invariável.

Escolha de um membro StringComparison para a chamada de método

A tabela a seguir descreve o mapeamento do contexto de cadeia de caracteres semântica para um membro StringComparison de enumeração:

Dados Comportamento System.StringComparison correspondente

value
Identificadores internos que diferenciam maiúsculas de minúsculas.

Identificadores que diferenciam maiúsculas e minúsculas nos padrões como XML e HTTP.

Configurações relacionadas à segurança que diferenciam maiúsculas de minúsculas.
Um identificador não linguístico, em que bytes correspondem exatamente. Ordinal
Identificadores internos que não diferenciam maiúsculas de minúsculas.

Identificadores que não diferenciam maiúsculas e minúsculas em padrões como XML e HTTP.

Caminhos de arquivo.

Chaves do Registro e valores.

Variáveis de ambiente.

Identificadores de recurso (por exemplo, nomes de identificador).

Configurações relacionadas à segurança que não diferenciam maiúsculas de minúsculas.
Um identificador não linguístico, em que as maiúsculas e minúsculas são irrelevantes; especialmente, dados armazenados na maioria dos serviços de sistema do Windows. OrdinalIgnoreCase
Alguns dados persistentes, linguisticamente relevantes.

Exibição de dados linguísticos que requer uma ordem de classificação fixa.
Dados independentes de cultura que ainda são linguisticamente relevantes. InvariantCulture

-ou-

InvariantCultureIgnoreCase
Dados exibidos para o usuário.

A maioria das entradas do usuário.
Dados que exigem os costumes linguísticos locais. CurrentCulture

-ou-

CurrentCultureIgnoreCase

Métodos comuns de comparação de cadeia de caracteres no .NET

As seções a seguir descrevem os métodos que são mais comumente usados para a comparação de cadeias de caracteres.

String.Compare

Interpretação padrão: StringComparison.CurrentCulture.

Como a operação mais central da interpretação de cadeia de caracteres, todas as instâncias de chamadas desse método devem ser examinadas para determinar se as cadeias de caracteres devem ser interpretadas de acordo com a cultura atual ou dissociadas da cultura (simbolicamente). Normalmente, é o último e uma comparação StringComparison.Ordinal deve ser usada em vez disso.

A classe System.Globalization.CompareInfo, que é retornada pelo CultureInfo.CompareInfo também inclui uma propriedade, um método Compare que fornece um grande número de opções correspondentes (ordinal, ignorando espaço em branco, ignorando tipo kana e assim por diante) por meio da enumeração de sinalizador CompareOptions.

String.CompareTo

Interpretação padrão: StringComparison.CurrentCulture.

Esse método no momento não oferece uma sobrecarga que especifica um tipo StringComparison. É possível converter esse método para o formulário recomendado String.Compare(String, String, StringComparison).

Os tipos que implementam as interfaces IComparable e IComparable<T> implementam este método. Como ele não oferece a opção de um parâmetro StringComparison, os tipos de implementação geralmente permitem que o usuário especifique um StringComparer em seu construtor. O exemplo a seguir define uma classe FileName cujo construtor de classe inclui um parâmetro StringComparer. Esse objeto StringComparer é usado no método FileName.CompareTo.

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);
   }
}
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

String.Equals

Interpretação padrão: StringComparison.Ordinal.

A classe String permite que você teste a igualdade chamando as sobrecargas do método Equals ou estáticas ou usando o operador de igualdade estático. O operador e as sobrecargas utilizam a comparação ordinal por padrão. No entanto, ainda é recomendável que você chame uma sobrecarga que especifique explicitamente o tipo StringComparison mesmo se você desejar executar uma comparação ordinal. Isso facilita a pesquisa de código para uma determinada interpretação da cadeia de caracteres.

String.ToUpper e String.ToLower

Interpretação padrão: StringComparison.CurrentCulture.

Tenha cuidado ao usar String.ToUpper()String.ToLower() os métodos e , pois forçar uma cadeia de caracteres em letras maiúsculas ou minúsculas geralmente é usado como uma normalização pequena para comparar cadeias de caracteres, independentemente do caso. Nesse caso, considere o uso de uma comparação que não diferencie maiúsculas de minúsculas.

Os métodos String.ToUpperInvariant e String.ToLowerInvariant também estão disponíveis. ToUpperInvariant é o modo padrão para normalizar maiúsculas e minúsculas. As comparações feitas usando StringComparison.OrdinalIgnoreCase são a composição de duas chamadas de maneira comportamental: chamar ToUpperInvariant em ambos os argumentos de cadeia de caracteres e fazer uma comparação usando StringComparison.Ordinal.

Também há sobrecargas disponíveis para converter para maiúsculas e minúsculas em uma cultura específica, passando um objeto CultureInfo que representa aquela cultura para o método.

Char.ToUpper e Char.ToLower

Interpretação padrão: StringComparison.CurrentCulture.

Os Char.ToUpper(Char) métodos e Char.ToLower(Char) funcionam de forma semelhante aos String.ToUpper() métodos e String.ToLower() descritos na seção anterior.

String.StartsWith e String.EndsWith

Interpretação padrão: StringComparison.CurrentCulture.

Por padrão, esses dois métodos executam uma comparação que leva em conta a cultura.

String.IndexOf e String.LastIndexOf

Interpretação padrão: StringComparison.CurrentCulture.

Há uma falta de consistência em como as sobrecargas padrão desses métodos realizam comparações. Todos os métodos String.IndexOf e String.LastIndexOf que incluem um parâmetro Char realizam uma comparação ordinal, mas os métodos padrão String.IndexOf e String.LastIndexOf que incluem um parâmetro String executam uma comparação que diferencia a cultura.

Se você chamar o método String.IndexOf(String) ou String.LastIndexOf(String) e passar a ele uma cadeia de caracteres para localizar na instância atual, é recomendável que você chame uma sobrecarga que especifique explicitamente o tipo StringComparison. As sobrecargas que incluem um argumento Char não permitem que você especifique um tipo StringComparison.

Métodos que realizam comparação indireta de cadeia de caracteres

Alguns métodos que não de cadeias de caracteres que têm a comparação de cadeia de caracteres como uma operação central usam o tipo StringComparer. A classe StringComparer inclui quatro propriedades estáticas que retornam instâncias StringComparer cujos métodos StringComparer.Compare realizam os seguintes tipos de comparações de cadeias de caracteres:

Array.Sort e Array.BinarySearch

Interpretação padrão: StringComparison.CurrentCulture.

Ao armazenar quaisquer dados em uma coleção ou ler dados persistentes de um arquivo ou banco de dados em uma coleção, alternar a cultura atual pode invalidar as invariáveis na coleção. O método Array.BinarySearch presume que os elementos na matriz a serem pesquisados já estão classificados. Para classificar qualquer elemento de cadeia de caracteres na matriz, o método Array.Sort chama o método String.Compare para ordenar os elementos individuais. Usar um comparador que leva em conta a cultura pode ser perigoso se a cultura mudar entre o momento em que a matriz é ordenada e que seu conteúdo é pesquisado. Por exemplo, no código a seguir, o armazenamento e a recuperação operam no comparador que é fornecido implicitamente pela propriedade Thread.CurrentThread.CurrentCulture. Se a cultura puder mudar entre as chamadas para StoreNames e DoesNameExist, e especialmente se os conteúdos da matriz forem mantidos em algum lugar entre as duas chamadas de método, a pesquisa binária poderá falhar.

// 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.
}
' 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

Uma variação recomendada é exibida no exemplo a seguir, que usa o mesmo método de comparação (que não leva em conta a cultura) ordinal para classificar e pesquisar a matriz. O código de alteração é refletido nas linhas rotuladas como Line A e Line B nos dois exemplos.

// 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.
}
' 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

Se esses dados forem mantidos e movidos entre as culturas e a classificação for usada para apresentar esses dados para o usuário, você pode considerar usar StringComparison.InvariantCulture, que opera linguisticamente para a melhor saída do usuário, mas não é afetado pelas alterações na cultura. O exemplo a seguir modifica os dois exemplos anteriores para usar a cultura invariável para classificar e pesquisar a matriz.

// 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.
}
' 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

Exemplo de coleções: Construtor de Hashtable

As cadeias de caracteres de hash fornecem um segundo exemplo de uma operação que é afetada pela maneira como as cadeias de caracteres são comparadas.

O exemplo a seguir cria um objeto Hashtable passando-o para o objeto StringComparer que é retornado pela propriedade StringComparer.OrdinalIgnoreCase. Como uma classe StringComparer que é derivada de StringComparer implementa a interface IEqualityComparer, seu método GetHashCode é usado para calcular o código hash de cadeias de caracteres na tabela de hash.

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);
   }
}
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

Confira também