Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O .NET fornece amplo suporte para o desenvolvimento de aplicativos localizados e globalizados e facilita a aplicação das convenções da cultura atual ou de uma cultura específica ao executar operações comuns, como classificar e exibir cadeias de caracteres. Mas classificar ou comparar cadeias de caracteres nem sempre é uma operação sensível à cultura. Por exemplo, cadeias de caracteres que são usadas internamente por um aplicativo normalmente devem ser tratadas de forma idêntica em todas as culturas. Quando dados de cadeia de caracteres culturalmente independentes, como marcas XML, marcas HTML, nomes de usuário, caminhos de arquivo e nomes de objetos do sistema, são interpretados como se fossem sensíveis à 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 casing de cadeia de caracteres no .NET, apresenta recomendações para selecionar um método de tratamento de cadeia de caracteres apropriado e fornece informações adicionais sobre métodos de tratamento de cadeia de caracteres.
Recomendações para uso de cadeia de caracteres
Ao desenvolver com o .NET, siga estas recomendações ao comparar cadeias de caracteres.
Dica
Vários métodos relacionados à cadeia de caracteres executam a comparação. Os exemplos incluem String.Equals, String.Compare, String.IndexOfe String.StartsWith.
- Use sobrecargas que especificam explicitamente as regras de comparação de cadeias de caracteres para operações de cadeia de caracteres. Normalmente, isso envolve chamar uma sobrecarga de método que possui um parâmetro do tipo StringComparison.
- Use StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase como padrão seguro para comparação de cadeias de caracteres sem considerar a cultura.
- Use comparações com StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase para melhorar o desempenho.
- Use operações de cadeia de caracteres baseadas em StringComparison.CurrentCulture quando você exibe a saída para o usuário.
- Use os valores não linguísticos StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase em vez de operações de cadeia de caracteres baseadas em CultureInfo.InvariantCulture quando a comparação é linguisticamente irrelevante (simbólica, por exemplo).
- Use o String.ToUpperInvariant método em vez do String.ToLowerInvariant método ao normalizar cadeias de caracteres para comparação.
- Use uma sobrecarga do String.Equals método para testar se duas cadeias de caracteres são iguais.
- Utilize os métodos String.Compare e String.CompareTo para classificar cadeias de caracteres, não para verificar a igualdade.
- Use a formatação sensível à cultura para exibir dados que não são de cadeia de caracteres, como números e datas, em uma interface do usuário. Use a formatação com a cultura invariável para persistir dados não textuais no formato de cadeia 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ê mantém dados linguisticamente significativos, mas culturalmente independentes.
- 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.
Especificar comparações de cadeia de caracteres explicitamente
A maioria dos métodos de manipulação de cadeia de caracteres no .NET está sobrecarregada. Normalmente, uma ou mais sobrecargas aceitam configurações padrão, enquanto outras não aceitam padrões e definem a maneira precisa 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 de tipo StringComparison, que é uma enumeração que especifica explicitamente regras para comparação de cadeia de caracteres por cultura e caso. A tabela a seguir descreve os membros da StringComparison enumeração.
StringComparison membro |
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 IndexOf método, que retorna o índice de uma subcadeia de caracteres em um String objeto que corresponde a um caractere ou uma cadeia de caracteres, tem nove sobrecargas:
- IndexOf(Char), IndexOf(Char, Int32) e IndexOf(Char, Int32, Int32), que, por padrão, realizam uma pesquisa de ordinal (diferencia maiúsculas de minúsculas e não diferencia a cultura) para um caractere na cadeia de caracteres.
- IndexOf(String), IndexOf(String, Int32) e IndexOf(String, Int32, Int32), que, por padrão, realizam uma pesquisa que diferencia maiúsculas de minúsculas e cultura de uma substring na cadeia de caracteres.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)e IndexOf(String, Int32, Int32, StringComparison), que incluem um parâmetro de tipo StringComparison que permite que a forma da comparação seja especificada.
Recomendamos que você selecione uma sobrecarga que não use 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 qual 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 está clara. No exemplo a seguir, que depende de padrões, é difícil saber se o desenvolvedor realmente pretendia uma comparação ordinal ou linguística de duas cadeias de caracteres ou se uma diferença de caso entre
url.Schemee "https" pode fazer com que o teste de igualdade retornefalse.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
Em geral, recomendamos que você chame um método que não dependa de padrões, pois ele torna a intenção do código inequívoca. Isso, por sua vez, torna o código mais legível e mais fácil de depurar e manter. O exemplo a seguir aborda as questões levantadas sobre o exemplo anterior. Isso deixa claro que a comparação ordinal é usada e que as diferenças no caso são ignoradas.
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
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 considera iguais. Uma boa interpretação é que nenhuma cadeia de caracteres é menor que a outra. As operações mais significativas que envolvem cadeias de caracteres incluem um ou ambos os procedimentos: comparar com outra cadeia de caracteres e executar 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 caractere usados em operações de classificação e comparação para sistemas operacionais Windows e a Tabela de Elementos 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 no Linux e no macOS depende da versão das bibliotecas de Componentes Internacionais para Unicode instaladas no sistema. Para obter informações sobre as versões da UTI e as versões unicode que elas implementam, consulte Baixar a UTI.
No entanto, avaliar duas cadeias de caracteres para 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 diferentes versões 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, consulte Strings e o Unicode Standard.
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. Comparações baseadas na cultura atual usam a cultura ou a localidade atual do thread. Se a cultura não for definida pelo usuário, ela usará como padrão a configuração do sistema operacional. Você sempre deve usar comparações baseadas na cultura atual quando os dados são linguisticamente relevantes e quando refletem a interação do usuário sensível à 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 no qual o aplicativo foi desenvolvido ou quando o thread em execução altera sua cultura. Esse comportamento é intencional, mas não é óbvio para muitos desenvolvedores. O exemplo a seguir ilustra diferenças na ordem de classificação entre as culturas de inglês dos EUA ("en-US") e sueco ("sv-SE"). Observe que as palavras "ångström", "Windows" e "Visual Studio" aparecem em posições diferentes nas matrizes de cadeia de caracteres classificadas.
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
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 também em como as classificações são ordenadas.
Comparações que usam semântica de cultura atual são o padrão para os seguintes métodos:
- Sobrecargas de String.Compare que não incluem um parâmetro StringComparison.
- SobrecargasString.CompareTo.
- O método padrão String.StartsWith(String) e o String.StartsWith(String, Boolean, CultureInfo) método com um
nullCultureInfo parâmetro. - O método padrão String.EndsWith(String) e o String.EndsWith(String, Boolean, CultureInfo) método com um
nullCultureInfo parâmetro. - Sobrecargas de String.IndexOf que aceitam um String como um parâmetro de pesquisa e que não têm um parâmetro StringComparison.
- Sobrecargas de String.LastIndexOf que aceitam um String como um parâmetro de pesquisa e que não têm um parâmetro StringComparison.
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 Turkish-I.
Para quase todos os alfabetos latinos, incluindo inglês americano, o caractere "i" (\u0069) é a versão minúscula 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 ponto" "İ" (\u0130), que é a versão maiúscula de "i". O turco também inclui um caractere "i sem ponto" minúsculo, "ı" (\u0131), que capitaliza "I". Esse comportamento também ocorre na cultura do Azerbaijão ("az").
Portanto, convenções sobre usar "i" maiúscula ou minúscula não são válidas entre diferentes 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 não forem linguísticos, o uso das sobrecargas padrão poderá produzir resultados indesejáveis, como ilustra a tentativa a seguir de executar uma comparação que não diferencia maiúsculas de minúsculas das cadeias de caracteres "bill" e "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
Essa comparação poderá causar problemas significativos se a cultura for usada inadvertidamente em 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 inglês dos EUA, mas false se a cultura atual for turca. Assim, em sistemas turcos, alguém poderia contornar 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) =>
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 e sem diferenciação de cultura, o código deve ser escrito conforme mostrado no exemplo a seguir:
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
Operações ordinais da cadeia de caracteres
Especificar o valor StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase em uma chamada de método significa uma comparação não linguística, na qual são ignorados os recursos das linguagens naturais. 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 ajusta melhor à interpretação pretendida de cadeias de caracteres, tornando o código mais rápido e confiável.
Comparações ordinais são comparações de cadeia de caracteres em que cada byte de cada cadeia de caracteres é comparado sem interpretação linguística; por exemplo, "windows" não corresponde a "Windows". Essa é essencialmente uma chamada para a função de runtime strcmp 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 não aplica regras linguísticas ao determinar um resultado.
As cadeias de caracteres no .NET podem conter caracteres nulos inseridos (e outros caracteres que não são de impressão). Uma das diferenças mais claras entre a comparação ordinal e sensível à cultura (incluindo comparações que usam a cultura invariável) diz respeito ao tratamento 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 executar comparações sensíveis à cultura (incluindo comparações que usam a cultura invariável). Como resultado, cadeias de caracteres que contêm caracteres nulos inseridos podem ser consideradas iguais às cadeias de caracteres que não contêm. Caracteres não imprimíveis inseridos podem ser ignorados para fins de métodos de comparação de cadeia de caracteres, como String.StartsWith.
Importante
Embora os métodos de comparação de cadeia de caracteres desconsiderem caracteres nulos inseridos, os métodos de pesquisa de cadeia de caracteres, como String.Contains, String.EndsWith, String.IndexOfe String.LastIndexOfString.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:
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
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:
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
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 seguinte comparação:
string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
é equivalente a (mas mais rápido do que) essa 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.
Ambos StringComparison.Ordinal e StringComparison.OrdinalIgnoreCase usam os valores binários diretamente e são mais adequados para comparação. Quando você não tiver certeza sobre suas configurações de comparação, use um desses dois valores. No entanto, como eles executam uma comparação byte por byte, eles não classificam por uma ordem de classificação linguística (como um dicionário inglês), mas por uma ordem de classificação binária. Os resultados podem parecer ímpares na maioria dos contextos se exibidos 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
Comparações com a cultura invariável usam a CompareInfo propriedade retornada pela propriedade estática CultureInfo.InvariantCulture . Esse comportamento é o mesmo em todos os sistemas; ele converte todos os caracteres fora de seu intervalo no que acredita serem 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.
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 precisam ser interpretadas como um conjunto de bytes. O CultureInfo.InvariantCulture.CompareInfo objeto faz com que o Compare método interprete determinados conjuntos de caracteres como equivalentes. Por exemplo, a seguinte equivalência é válida na cultura invariável:
InvariantCulture: a + ̊ = å
O caractere de LETRA A MINÚSCULA LATINA "a" (\u0061), quando está próximo ao CARACTERE DE ANEL SUPERIOR COMBINÁVEL "+ " ̊" (\u030a), é interpretado como a LETRA A MINÚSCULA LATINA COM O CARACTERE DE ANEL SUPERIOR "å" (\u00e5). Como mostra o exemplo a seguir, esse comportamento difere da comparação ordinal.
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine($"Equal sort weight of {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");
Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {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
Ao interpretar nomes de arquivo, cookies ou qualquer outra coisa em que uma combinação como "å" possa aparecer, as comparações ordinais ainda oferecem o comportamento mais transparente e adequado.
Em equilíbrio, a cultura invariável tem poucas propriedades que a tornam útil para comparação. Ele faz a comparação de maneira linguisticamente relevante, o que o impede de garantir equivalência simbólica completa, mas não é a escolha para exibição em nenhuma cultura. Um dos poucos motivos para usar StringComparison.InvariantCulture para comparação é a persistência de dados ordenados para uma exibição que seja 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.
Como escolher um membro de StringComparison
A tabela a seguir descreve o mapeamento do contexto semântico da cadeia de caracteres para um membro de enumeração StringComparison:
| Dados | Comportamento | System.StringComparison correspondente valor |
|---|---|---|
| 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 os 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, onde o caso é irrelevante. | OrdinalIgnoreCase |
| Alguns dados persistentes e linguisticamente relevantes. Exibição de dados linguísticos que exigem uma ordem de classificação fixa. |
Dados culturalmente independentes 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 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 mais usados para comparação de cadeias de caracteres.
String.Compare
Interpretação padrão: StringComparison.CurrentCulture.
Sendo a operação mais central na interpretação de cadeias de caracteres, todas as instâncias dessas chamadas aos métodos devem ser examinadas para determinar se as cadeias de caracteres devem ser interpretadas de acordo com a cultura atual ou se devem ser interpretadas sem considerar o contexto cultural (simbolicamente). Normalmente, é o último caso, e deve-se usar uma comparação StringComparison.Ordinal como alternativa.
A classe System.Globalization.CompareInfo, que é retornada pela propriedade CultureInfo.CompareInfo, também inclui um método Compare que fornece um grande número de opções de correspondência (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. Normalmente, é possível converter esse método no formulário recomendado String.Compare(String, String, StringComparison) .
Os tipos que implementam as interfaces IComparable e IComparable<T> implementam esse 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 FileName classe cujo construtor de classe inclui um StringComparer parâmetro. Esse StringComparer objeto é então usado no FileName.CompareTo método.
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
Interpretação padrão: StringComparison.Ordinal.
A classe String permite que você teste a igualdade utilizando as sobrecargas de método estáticas ou de instância Equals, 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 esses métodos String.ToUpper() e String.ToLower(), pois forçar uma cadeia de caracteres para maiúsculas ou minúsculas normalmente é usado como uma normalização pequena para comparar cadeias de caracteres independentemente de maiúsculas e minúsculas. Nesse caso, considere o uso de uma comparação que não diferencie maiúsculas de minúsculas.
Os String.ToUpperInvariant métodos e os métodos 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 métodos Char.ToUpper(Char) e Char.ToLower(Char) funcionam de forma semelhante aos métodos String.ToUpper() e String.ToLower() descritos na seção anterior.
String.StartsWith e String.EndsWith
Interpretação padrão: StringComparison.CurrentCulture.
Por padrão, ambos os métodos executam uma comparação sensível à cultura. Em particular, eles podem ignorar caracteres que não são impressos.
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 executam comparações. Todos os métodos String.IndexOf e String.LastIndexOf que incluem um parâmetro Char executam 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 sensível à 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 Char argumento não permitem que você especifique um StringComparison tipo.
MemoryExtensions.AsSpan.IndexOfAny e o tipo SearchValues<T>
O .NET 8 introduziu o SearchValues<T> tipo, que fornece uma solução otimizada para pesquisar conjuntos específicos de caracteres ou bytes em intervalos.
Se você estiver comparando uma cadeia de caracteres com um conjunto fixo de valores conhecidos repetidamente, considere usar o SearchValues<T>.Contains(T) método em vez de comparações encadeadas ou abordagens baseadas em LINQ.
SearchValues<T> pode pré-compilar estruturas de pesquisa internas e otimizar a lógica de comparação com base nos valores fornecidos. Para ver os benefícios de desempenho, crie e armazene a instância em cache uma vez e reutilize-a SearchValues<string> para comparações:
using System.Buffers;
private static readonly SearchValues<string> Commands = SearchValues.Create(
new[] { "start", "run", "go", "begin", "commence" },
StringComparison.OrdinalIgnoreCase);
if (Commands.Contains(command))
{
// ...
}
No .NET 9, SearchValues foi estendido para dar suporte à pesquisa de subcadeias de caracteres dentro de uma cadeia de caracteres maior. Para obter um exemplo, consulte SearchValues expansão.
Métodos que executam a comparação de cadeia de caracteres indiretamente
Alguns métodos que não são de cadeia de caracteres que têm a comparação de cadeia de caracteres como uma operação central usam o StringComparer tipo. A StringComparer classe inclui seis propriedades estáticas que retornam StringComparer instâncias cujos StringComparer.Compare métodos executam os seguintes tipos de comparações de cadeia de caracteres:
- Comparações de cadeias de caracteres que levam em conta a cultura usando a cultura atual. Esse StringComparer objeto é retornado pela StringComparer.CurrentCulture propriedade.
- Comparações que não diferenciam maiúsculas de minúsculas usando a cultura atual. Esse StringComparer objeto é retornado pela StringComparer.CurrentCultureIgnoreCase propriedade.
- Comparações insensíveis à cultura usando as regras de comparação de palavras da cultura invariante. Esse StringComparer objeto é retornado pela StringComparer.InvariantCulture propriedade.
- Comparações que não diferenciam maiúsculas e minúsculas e a cultura usando as regras de comparação de palavras da cultura invariável. Esse StringComparer objeto é retornado pela StringComparer.InvariantCultureIgnoreCase propriedade.
- Comparação ordinal. Esse StringComparer objeto é retornado pela StringComparer.Ordinal propriedade.
- Comparação ordinal que não diferencia maiúsculas de minúsculas. Esse StringComparer objeto é retornado pela StringComparer.OrdinalIgnoreCase propriedade.
Array.Sort e Array.BinarySearch
Interpretação padrão: StringComparison.CurrentCulture.
Quando você armazena qualquer dado em uma coleção ou lê dados persistentes de um arquivo ou banco de dados para uma coleção, mudar a cultura atual pode invalidar os invariantes na coleção. O Array.BinarySearch método pressupõe que os elementos na matriz a ser pesquisada já estão classificados. Para classificar qualquer elemento de cadeia de caracteres na matriz, o Array.Sort método chama o String.Compare método para ordenar elementos individuais. O uso de um comparador sensível à cultura poderá ser perigoso se a cultura mudar entre o tempo em que a matriz é classificada e seu conteúdo for pesquisado. Por exemplo, no código a seguir, o armazenamento e a recuperação operam no comparador fornecido implicitamente pela Thread.CurrentThread.CurrentCulture propriedade. 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)
{
_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
Uma variação recomendada aparece no exemplo a seguir, que usa o mesmo método de comparação ordinal (que não diferencia cultura) tanto para classificar quanto pesquisar a matriz. O código de alteração é refletido nas linhas rotuladas Line A e Line B nos dois exemplos.
// 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
Se esses dados forem persistidos e movidos entre culturas, e a classificação for usada para apresentar esses dados ao usuário, você poderá considerar o uso de StringComparison.InvariantCulture, que funciona linguisticamente para melhores resultados para o usuário, embora não seja afetado por mudanças culturais. 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)
{
_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
Exemplo de coleções: Hashtable construtor
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 instancia um objeto Hashtable passando o objeto StringComparer, que é retornado pela propriedade StringComparer.OrdinalIgnoreCase. Como uma classe StringComparer derivada de StringComparer implementa a IEqualityComparer interface, seu GetHashCode método é usado para calcular o código hash de cadeias de caracteres na tabela de hash.
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