Compartilhar via


Como comparar cadeias de caracteres em 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?"

Os seguintes fatores complicam estas duas perguntas:

  • Você pode escolher uma comparação ordinal ou linguística.
  • Você pode escolher se o caso importa.
  • 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: comparar 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: comparar cadeias de caracteres usando regras de classificação sensíveis à cultura e a cultura invariável.
  • InvariantCultureIgnoreCase: compare cadeias de caracteres usando regras de classificação sensíveis à cultura, a cultura invariável e ignorando o caso das cadeias de caracteres sendo comparadas.
  • Ordinal: comparar cadeias de caracteres usando regras de classificação ordinais (binárias).
  • OrdinalIgnoreCase: compara strings usando regras de classificação ordinais (binárias) e ignorando o caso das strings que estão sendo comparadas.

Ao comparar cadeias de caracteres, você define uma ordem entre elas. 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 para software quanto para humanos. Outras comparações podem verificar se as cadeias de caracteres são as mesmas. Essas verificações de mesmez são semelhantes à igualdade, mas algumas diferenças, como diferenças de caso, 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 para igualdade com String.Equals e os operadores == e != difere da comparação de strings usando os métodos String.CompareTo e Compare(String, String). Todos eles executam uma comparação sensível a maiúsculas e minúsculas. No entanto, enquanto os testes de igualdade executam uma comparação ordinal, os métodos CompareToCompare efetuam uma comparação linguística com sensibilidade cultural usando a cultura atual. Deixe a intenção do seu código clara ao utilizar uma sobrecarga que especifica explicitamente o tipo de comparação a ser realizada.

Você pode usar o is operador e um padrão constante como uma alternativa para == quando o operando à direita é uma constante.

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 uma comparação ordinal insensível a maiúsculas e 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 StringComparison argumento. Essas comparações são mostradas no seguinte código:

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 executar uma comparação ordinal que não diferencia maiúsculas de minúsculas.

Comparações linguísticas

Muitos métodos de comparação de cadeia 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 é conhecida como "ordem de classificação de palavras". Quando você executa uma comparação linguística, alguns caracteres Unicode não numé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 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 Char instâncias. 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 alemão Esszet: "ß" 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 de NLS (Suporte à Linguagem Nacional ). No .NET 5 e versões posteriores, as APIs de globalização do .NET usam as bibliotecas International Components for Unicode (ICU), que unificam o comportamento de globalização do .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 executadas usando um CultureInfo objeto para garantir uma comparação específica da cultura. A cultura usada afeta 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");
    }
}

Comparações sensíveis à cultura normalmente são usadas para comparar e classificar a entrada de cadeias de caracteres por 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. Até 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ê utiliza os métodos estáticos Array que recebem um parâmetro System.StringComparer.

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

string[] lines =
[
    @"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 for classificada, você poderá pesquisar 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 pela metade. 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 tiver sido encontrada, o valor retornado indicará onde ela estaria se fosse encontrada.

string[] lines =
[
    @"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 System.Collections.Generic.List<T> coleção 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 compare e ordene duas cadeias de caracteres. O String.CompareTo método 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 sensível a maiúsculas e minúsculas. Você usaria os métodos estáticos String.Compare para especificar regras de comparação diferentes.

List<string> lines =
[
    @"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 ShowWhere local mostra onde o texto procurado está ou estaria:

List<string> lines =
[
    @"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}.");
    }
}

Sempre certifique-se de usar o mesmo tipo de comparação para classificação e pesquisa. O uso de diferentes tipos 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 tomam 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 um StringComparer.Ordinal ou StringComparer.OrdinalIgnoreCase.

Consulte também