Partilhar via


Como comparar cadeias de caracteres em C#

Você compara strings para responder a uma de duas perguntas: "Essas duas strings são iguais?" ou "Em que ordem essas strings devem ser colocadas ao classificá-las?"

Essas duas perguntas são complicadas por fatores que afetam as comparações de cadeia de caracteres:

  • Você pode escolher uma comparação ordinal ou linguística.
  • Você pode escolher se o caso é importante.
  • Você pode escolher comparações específicas da cultura.
  • As comparações linguísticas dependem da cultura e da plataforma.

Os System.StringComparison campos de enumeração representam estas opções:

  • CurrentCulture: compare cadeias de caracteres usando regras de classificação sensíveis à cultura e a cultura atual.
  • CurrentCultureIgnoreCase: compare cadeias de caracteres usando regras de classificação sensíveis à cultura, a cultura atual e ignorando o caso das cadeias de caracteres que estão sendo comparadas.
  • InvariantCulture: compare cadeias de caracteres usando regras de classificação sensíveis à cultura e a cultura invariante.
  • InvariantCultureIgnoreCase: compare cadeias de caracteres usando regras de classificação sensíveis à cultura, a cultura invariante e ignorando o caso das cadeias de caracteres que estão sendo comparadas.
  • Ordinal: Compare cadeias de caracteres usando regras de classificação ordinais (binárias).
  • OrdinalIgnoreCase: Compare cadeias de caracteres usando regras de classificação ordinais (binárias) e ignorando o caso das cadeias de caracteres que estão sendo comparadas.

Nota

Os exemplos de C# neste artigo são executados no Try.NET corredor de código embutido e playground. Selecione o botão Executar para executar um exemplo em uma janela interativa. Depois de executar o código, você pode modificá-lo e executar o código modificado selecionando Executar novamente. O código modificado é executado na janela interativa ou, se a compilação falhar, a janela interativa exibe todas as mensagens de erro do compilador C#.

Ao comparar cadeias de caracteres, você define uma ordem entre elas. As comparações são usadas para classificar uma sequência de cadeias de caracteres. Uma vez que a sequência está em uma ordem conhecida, é mais fácil pesquisar, tanto por software quanto por humanos. Outras comparações podem verificar se as cadeias de caracteres são as mesmas. Essas verificações de mesmice são semelhantes à igualdade, mas algumas diferenças, como diferenças de casos, podem ser ignoradas.

Comparações ordinais padrão

Por padrão, as operações mais comuns:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

result = root.Equals(root2, StringComparison.Ordinal);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not equal")}");

A comparação ordinal padrão não leva em conta as regras linguísticas ao comparar cadeias de caracteres. Ele compara o valor binário de cada Char objeto em duas cadeias de caracteres. Como resultado, a comparação ordinal padrão também diferencia maiúsculas de minúsculas.

O teste de igualdade com String.Equals e os == operadores e != difere da comparação de cadeia de caracteres usando os String.CompareTo métodos e Compare(String, String) . Todos eles realizam uma comparação que diferencia maiúsculas de minúsculas. No entanto, enquanto os testes de igualdade realizam uma comparação ordinal, os CompareTo métodos e Compare realizam uma comparação linguística consciente da cultura usando a cultura atual. Deixe clara a intenção do seu código chamando uma sobrecarga que especifica explicitamente o tipo de comparação a ser executada.

Comparações ordinais que não diferenciam maiúsculas de minúsculas

O String.Equals(String, StringComparison) método permite especificar um StringComparison valor de para uma comparação ordinal que não diferencia maiúsculas de StringComparison.OrdinalIgnoreCase minúsculas. Há também um método estático String.Compare(String, String, StringComparison) que executa uma comparação ordinal que não diferencia maiúsculas de minúsculas se você especificar um valor de StringComparison.OrdinalIgnoreCase para o StringComparison argumento. Essas comparações são mostradas no código a seguir:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);
bool areEqual = String.Equals(root, root2, StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType: StringComparison.OrdinalIgnoreCase);

Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not equal.")}");
if (comparison < 0)
    Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
    Console.WriteLine($"<{root}> is greater than <{root2}>");
else
    Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");

Esses métodos usam as convenções de invólucro da cultura invariante ao realizar uma comparação ordinal que não diferencia maiúsculas de minúsculas.

Comparações linguísticas

Muitos métodos de comparação de cadeias de caracteres (como String.StartsWith) usam regras linguísticas para a cultura atual por padrão para ordenar suas entradas. Esta comparação linguística é por vezes referida como "ordem de ordenação das palavras". Quando você executa uma comparação linguística, alguns caracteres Unicode não alfanuméricos podem ter pesos especiais atribuídos. Por exemplo, o hífen "-" pode ter um pequeno peso atribuído a ele para que "co-op" e "coop" apareçam um ao lado do outro em ordem de classificação. Alguns caracteres de controle não imprimíveis podem ser ignorados. Além disso, alguns caracteres Unicode podem ser equivalentes a uma sequência de Char instâncias. O exemplo a seguir usa a frase "Eles dançam na rua." em alemão com o "ss" (U+0073, U+0073) em uma corda e 'ß' (U+00DF) em outra. Linguisticamente (no Windows), "ss" é igual ao alemão Esszet: caractere 'ß' nas culturas "en-US" e "de-DE".

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

bool equal = String.Equals(first, second, StringComparison.InvariantCulture);
Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")} equal.");
showComparison(first, second);

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
    int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
    else if (compareLinguistic > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
    if (compareOrdinal < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    else if (compareOrdinal > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

No Windows, antes do .NET 5, a ordem de classificação de "cop", "coop" e "co-op" muda quando você muda de uma comparação linguística para uma comparação ordinal. As duas frases alemãs também se comparam de forma diferente usando os diferentes tipos de comparação. Antes do .NET 5, as APIs de globalização do .NET usavam bibliotecas NLS (National Language Support). No .NET 5 e versões posteriores, as APIs de globalização do .NET usam bibliotecas ICU (International Components for Unicode), que unifica o . O comportamento de globalização da NET em todos os sistemas operacionais suportados.

Comparações utilizando culturas específicas

O exemplo a seguir armazena CultureInfo objetos para as culturas en-US e de-DE. As comparações são realizadas usando um CultureInfo objeto para garantir uma comparação específica da cultura. A cultura utilizada afeta as comparações linguísticas. O exemplo a seguir mostra os resultados da comparação das duas frases alemãs usando a cultura "en-US" e a cultura "de-DE":

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

var en = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare
// overload that takes a StringComparison value.
int i = String.Compare(first, second, en, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");

var de = new System.Globalization.CultureInfo("de-DE");
i = String.Compare(first, second, de, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");

bool b = String.Equals(first, second, StringComparison.CurrentCulture);
Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words, en);
showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo culture)
{
    int compareLinguistic = String.Compare(one, two, en, System.Globalization.CompareOptions.None);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using en-US culture");
    else if (compareLinguistic > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using en-US culture");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using en-US culture");
    if (compareOrdinal < 0)
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    else if (compareOrdinal > 0)
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    else
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}

As comparações sensíveis à cultura são normalmente usadas para comparar e classificar cadeias de caracteres inseridas pelos usuários com outras cadeias de caracteres inseridas pelos usuários. Os caracteres e as convenções de classificação dessas cadeias de caracteres podem variar dependendo da localidade do computador do usuário. Mesmo cadeias de caracteres que contêm caracteres idênticos podem ser classificadas de forma diferente, dependendo da cultura do thread atual.

Classificação linguística e pesquisa de cadeias de caracteres em matrizes

Os exemplos a seguir mostram como classificar e pesquisar cadeias de caracteres em uma matriz usando uma comparação linguística dependente da cultura atual. Você usa os métodos estáticos Array que usam um System.StringComparer parâmetro.

O exemplo a seguir mostra como classificar uma matriz de cadeias de caracteres usando a cultura atual:

string[] lines = new string[]
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

// Specify Ordinal to demonstrate the different behavior.
Array.Sort(lines, StringComparer.CurrentCulture);

foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Uma vez que a matriz é classificada, você pode procurar entradas usando uma pesquisa binária. Uma pesquisa binária começa no meio da coleção para determinar qual metade da coleção conteria a cadeia de caracteres procurada. Cada comparação subsequente subdivide a parte restante da coleção ao meio. A matriz é classificada usando o StringComparer.CurrentCulture. A função ShowWhere local exibe informações sobre onde a cadeia de caracteres foi encontrada. Se a cadeia de caracteres não foi encontrada, o valor retornado indica onde ela estaria se fosse encontrada.

string[] lines = new string[]
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(T[] array, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
            Console.Write("beginning of sequence and ");
        else
            Console.Write($"{array[index - 1]} and ");

        if (index == array.Length)
            Console.WriteLine("end of sequence.");
        else
            Console.WriteLine($"{array[index]}.");
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

Classificação ordinal e pesquisa em coleções

O código a seguir usa a System.Collections.Generic.List<T> classe collection para armazenar cadeias de caracteres. As cadeias de caracteres são classificadas usando o List<T>.Sort método. Esse método precisa de um delegado que compara e ordena duas cadeias de caracteres. O String.CompareTo método fornece essa função de comparação. Execute a amostra e observe a ordem. Essa operação de classificação usa uma classificação ordinal que diferencia maiúsculas de minúsculas. Você usaria os métodos estáticos String.Compare para especificar diferentes regras de comparação.

List<string> lines = new List<string>
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

lines.Sort((left, right) => left.CompareTo(right));
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Uma vez classificada, a lista de strings pode ser pesquisada usando uma pesquisa binária. O exemplo a seguir mostra como pesquisar a lista classificada usando a mesma função de comparação. A função ShowWhere local mostra onde o texto procurado está ou seria:

List<string> lines = new List<string>
{
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(IList<T> collection, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
            Console.Write("beginning of sequence and ");
        else
            Console.Write($"{collection[index - 1]} and ");

        if (index == collection.Count)
            Console.WriteLine("end of sequence.");
        else
            Console.WriteLine($"{collection[index]}.");
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

Certifique-se sempre de usar o mesmo tipo de comparação para classificar e pesquisar. O uso de diferentes tipos de comparação para classificar e pesquisar produz resultados inesperados.

Colete classes como System.Collections.Hashtable, System.Collections.Generic.Dictionary<TKey,TValue>e System.Collections.Generic.List<T> tem construtores que usam um System.StringComparer parâmetro quando o tipo dos elementos ou chaves é string. Em geral, você deve usar esses construtores sempre que possível e especificar um StringComparer.Ordinal ou StringComparer.OrdinalIgnoreCase.

Consulte também