Como comparar cadeias de caracteres no C#

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

Essas duas perguntas são complicadas devido a 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 uso de maiúsculas faz diferença.
  • Você pode escolher comparações específicas de cultura.
  • As comparações linguísticas dependem da plataforma e da cultura.

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

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

Observação

Os exemplos de C# neste artigo são executados no executador de código embutido Try.NET e no playground. Clique no botão Executar para executar um exemplo em uma janela interativa. Ao executar o código, é possível modificá-lo e executar o código modificado clicando em Executar novamente. O código modificado será executado na janela interativa ou, se a compilação falhar, a janela interativa exibirá todos as mensagens de erro do compilador C#.

Ao comparar cadeias de caracteres, você pode definir 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 esteja em uma ordem conhecida, fica mais fácil pesquisar, tanto por software quanto por humanos. Outras comparações podem verificar se as strings são iguais. Essas verificações de semelhança são semelhantes à igualdade, mas algumas diferenças, como diferenças de maiúsculas e minúsculas, podem ser ignoradas.

Comparações ordinárias 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 regras linguísticas ao comparar cadeias de caracteres. Ela compara o valor binário de cada objeto Char em duas cadeias de caracteres. Como resultado, a comparação ordinal padrão também diferencia maiúsculas de minúsculas.

Observe que o teste de igualdade com String.Equals e os operadores == e != difere da comparação de cadeias de caracteres usando os métodos String.CompareTo e Compare(String, String). Todos eles executam uma comparação que diferencia maiúsculas de minúsculas. No entanto, enquanto os testes de igualdade executam uma comparação ordinal, os métodos CompareTo e Compare executam uma comparação linguística com reconhecimento de 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 método String.Equals(String, StringComparison) permite que você especifique um valor StringComparison de StringComparison.OrdinalIgnoreCase para especificar uma comparação que não diferencia maiúsculas de 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 StringComparison.OrdinalIgnoreCase para o argumento StringComparison. 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 maiúsculas e minúsculas da cultura invariável ao realizar uma comparação ordinal sem distinção entre maiúsculas e 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. Essa comparação linguística às vezes é chamada de "ordem de classificação de 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 peso pequeno atribuído a ele para que "co-op" e "coop" apareçam um ao lado do outro na 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 instâncias Char. O exemplo a seguir usa a frase "Eles dançam na rua" em alemão com os "ss" (U+0073 U+0073) em uma cadeia de caracteres e 'ß' (U+00DF) em outra. Linguisticamente (no Windows), "ss" é igual ao caractere 'ß' Esszet alemão 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 em alemão também são comparadas de forma diferente ao usar os tipos diferentes de comparação. Antes do .NET 5, as APIs de globalização do .NET usavam bibliotecas National Language Support (NLS). No .NET 5 e versões posteriores, as APIs de globalização do .NET usam bibliotecas ICU (Componentes Internacionais para Unicode), que unificam o comportamento de globalização da NET em todos os sistemas operacionais com suporte.

Comparações usando culturas específicas

O exemplo a seguir armazena objetos CultureInfo para as culturas en-US e de-DE. As comparações são feitas usando um objeto CultureInfo para garantir uma comparação específica da cultura. A cultura usada afeta as comparações linguísticas. O exemplo a seguir mostra os resultados da comparação das duas frases em alemão 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 que diferenciam cultura normalmente são usadas para comparar e classificar cadeias de caracteres inseridas por usuários com outras cadeias de caracteres inseridas por usuários. Os caracteres e as convenções de classificação dessas cadeias de caracteres podem variar de acordo com a localidade do computador do usuário. Até mesmo cadeias de caracteres que contêm caracteres idênticos podem ser classificadas de formas diferentes dependendo da cultura do thread atual.

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

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

O exemplo a seguir mostra como classificar um array de strings 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}");
}

Depois que a matriz é classificada, você pode procurar as entradas usando uma pesquisa binária. Uma pesquisa binária é iniciada no meio da coleção para determinar qual metade da coleção contém a cadeia de caracteres procurada. Cada comparação subsequente subdivide a parte restante da coleção na metade. A matriz é classificada usando o StringComparer.CurrentCulture. A função local ShowWhere exibe informações sobre o local em que a cadeia de caracteres foi encontrada. Se a cadeia de caracteres não for encontrada, o valor retornado indicará 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 classe de coleção System.Collections.Generic.List<T> para armazenar cadeias de caracteres. As cadeias de caracteres são classificadas usando o método List<T>.Sort. Esse método precisa de um delegado que compara e ordena as duas cadeias de caracteres. O método String.CompareTo fornece essa função de comparação. Execute o exemplo e observe a ordem. Essa operação de classificação usa uma classificação ordinal que diferencia maiúsculas e minúsculas. Você usaria os métodos String.Compare estáticos para especificar regras de comparação diferentes.

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 cadeias de caracteres 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 local ShowWhere mostra o local em que o texto procurado está ou deveria estar:

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

Use sempre o mesmo tipo de comparação para classificação e pesquisa. O uso de tipos diferentes de comparação para classificação e pesquisa produz resultados inesperados.

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

Confira também