Cómo comparar cadenas en C#

Las cadenas se comparan para responder a una de estas dos preguntas: "¿Son estas dos cadenas iguales?" o "¿En qué orden deben colocarse estas cadenas al ordenarlas?"

Esas dos preguntas se complican por factores que influyen en las comparaciones de cadenas:

  • Puede elegir una comparación ordinal o lingüística.
  • Puede elegir si distingue entre mayúsculas y minúsculas.
  • Puede elegir comparaciones específicas de referencia cultural.
  • Las comparaciones lingüísticas dependen de la plataforma y la referencia cultural.

Los campos de enumeración System.StringComparison representan estas opciones:

  • CurrentCulture: compara cadenas mediante las reglas de ordenación de la referencia cultural y la referencia cultural actual.
  • CurrentCultureIgnoreCase: compara cadenas mediante las reglas de ordenación de la referencia cultural y la referencia cultural actual, e ignora el uso de mayúsculas y minúsculas de las cadenas que se comparan.
  • InvariantCulture: compara cadenas mediante reglas de ordenación que distinguen referencias culturales y la referencia cultural invariable.
  • InvariantCultureIgnoreCase: compara cadenas mediante reglas de ordenación que distinguen referencias culturales, la referencia cultural invariable e ignorando el caso de las cadenas que se comparan.
  • Ordinal: compara cadenas mediante las reglas de ordenación ordinal (binaria).
  • OrdinalIgnoreCase: compara cadenas mediante las reglas de ordenación ordinal (binaria) e ignora el uso de mayúsculas y minúsculas de las cadenas que se comparan.

Nota:

Los ejemplos de C# de este artículo se ejecutan en el ejecutor de código en línea y área de juegos de Try.NET. Haga clic en el botón Ejecutar para ejecutar un ejemplo en una ventana interactiva. Una vez que se ejecuta el código, puede modificar y ejecutar el código modificado si vuelve a hacer clic en Ejecutar. El código modificado se ejecuta en la ventana interactiva o, si se produce un error en la compilación, en la ventana interactiva se muestran todos los mensajes de error del compilador de C#.

Cuando se comparan cadenas, se define un orden entre ellas. Las comparaciones se usan para ordenar una secuencia de cadenas. Una vez que la secuencia está en un orden conocido, es más fácil hacer búsquedas, tanto para el software como para las personas. Otras comparaciones pueden comprobar si las cadenas son iguales. Estas comprobaciones de similitud son parecidas a la igualdad, pero pueden omitirse algunas diferencias, como las diferencias entre mayúsculas y minúsculas.

Comparaciones de ordinales predeterminadas

De forma predeterminada, las operaciones más comunes:

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

La comparación de ordinales predeterminada no tiene en cuenta reglas lingüísticas cuando se comparan cadenas. Compara el valor binario de cada objeto Char en dos cadenas. Como resultado, la comparación de ordinales predeterminada también distingue mayúsculas de minúsculas.

La prueba de igualdad con String.Equals y los operadores == y != es diferente de la comparación de cadenas que usa los métodos String.CompareTo y Compare(String, String). Todas realizan una comparación que distingue mayúsculas de minúsculas. Sin embargo, aunque las pruebas de igualdad realizan una comparación ordinal, los métodos CompareTo y Compare realizan una comparación lingüística, que tiene en cuenta la referencia cultural, mediante la referencia cultural actual. Haga que la intención del código sea clara llamando a una sobrecarga que especifica explícitamente el tipo de comparación que se va a realizar.

Comparaciones de ordinales sin distinción entre mayúsculas y minúsculas

Con el método String.Equals(String, StringComparison) puede especificar un valor StringComparison de StringComparison.OrdinalIgnoreCase para una comparación ordinal que no distingue entre mayúsculas y minúsculas. También hay un método String.Compare(String, String, StringComparison) estático que realiza una comparación ordinal que distingue mayúsculas de minúsculas. Para usarlo, debe especificar un valor de StringComparison.OrdinalIgnoreCase para el argumento StringComparison. Estas comparaciones se muestran en el código siguiente:

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

Estos métodos utilizan las convenciones de mayúsculas y minúsculas de la cultura invariable cuando realizan una comparación ordinal insensible a mayúsculas y minúsculas.

Comparaciones lingüísticas

Muchos métodos de comparación de cadenas (como String.StartsWith) usan reglas lingüísticas para la referencia cultural actual de forma predeterminada para ordenar sus entradas. Esta comparación lingüística se conoce a veces como "criterio de ordenación de palabras". Cuando se realiza una comparación lingüística, algunos caracteres Unicode no alfanuméricos pueden tener asignados pesos especiales. Por ejemplo, el guion ("-") podría tener asignado un peso pequeño, por lo que las cadenas "coop" y "co-op" aparecerían una junto a la otra en una ordenación. Es posible que se omitan algunos caracteres de control que no son de impresión. Además, algunos caracteres Unicode pueden ser equivalentes a una secuencia de instancias de Char. En el ejemplo siguiente se usa la frase "Ellos bailan en la calle" en alemán. Usa "ss" (U+0073 U+0073) en una cadena y "ß" (U+00DF) en otra. Lingüísticamente (en Windows), "ss" es igual que el carácter "ß" en alemán en las referencias culturales "en-US" y "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");
}

En Windows, antes de .NET 5, el criterio de ordenación de "cop", "coop" y "co-op" varía al cambiar de una comparación lingüística a una comparación ordinal. Las dos frases en alemán también se comparan de manera diferente mediante tipos de comparación diferentes. Antes de .NET 5, las API de globalización de .NET usaban bibliotecas de compatibilidad con idiomas nacionales (NLS). En .NET 5 y versiones posteriores, las API de globalización de .NET usan componentes internacionales para bibliotecas de Unicode (ICU), que unifican el comportamiento de globalización de NET en todos los sistemas operativos compatibles.

Comparaciones con referencias culturales específicas

En el ejemplo siguiente se almacenan objetos CultureInfo para las referencias culturales en-US y de-DE. Las comparaciones se realizan con el objeto CultureInfo para garantizar una comparación específica de la referencia cultural. La referencia cultural usada afecta a las comparaciones lingüísticas. En este ejemplo se muestra el resultado de comparar las dos frases en alemán usando la referencia cultural "en-US" y la referencia cultural "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");
}

Las comparaciones dependientes de la referencia cultural se usan normalmente para comparar y ordenar cadenas escritas por usuarios con otras cadenas escritas por usuarios. Los caracteres y las convenciones de ordenación de estas cadenas pueden variar según la configuración regional del equipo del usuario. Incluso las cadenas que contienen caracteres idénticos podrían ordenarse de forma diferente en función de la referencia cultural del subproceso actual.

Ordenación lingüística y búsqueda de cadenas en matrices

En estos ejemplos se muestra cómo ordenar y buscar cadenas en una matriz mediante una comparación lingüística que depende de la referencia cultural actual. Use los métodos Array estáticos que toman un parámetro System.StringComparer.

En el ejemplo siguiente se muestra cómo ordenar una matriz de cadenas mediante la referencia cultural actual:

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

Una vez que se ordena la matriz, puede buscar entradas mediante una búsqueda binaria. Una búsqueda binaria empieza en medio de la colección para determinar qué mitad de la colección debe contener la cadena buscada. Cada comparación posterior divide la parte restante de la colección por la mitad. La matriz se ordena con el elemento StringComparer.CurrentCulture. La función local ShowWhere muestra información sobre dónde se encuentra la cadena. Si no se encuentra la cadena, el valor devuelto indica dónde estaría si se encontrara.

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

Ordenación de ordinales y búsqueda en colecciones

Este código usa la clase de colección System.Collections.Generic.List<T> para almacenar cadenas. Las cadenas se ordenan mediante el método List<T>.Sort. Este método necesita un delegado que compare y ordene dos cadenas. El método String.CompareTo proporciona esa función de comparación. Ejecute el ejemplo y observe el orden. Esta operación de ordenación usa una ordenación ordinal con distinción entre mayúsculas y minúsculas. Tendría que usar los métodos estáticos String.Compare para especificar reglas de comparación distintas.

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

Una vez realizada la ordenación, se pueden hacer búsquedas en la lista de cadenas mediante una búsqueda binaria. En el ejemplo siguiente se muestra cómo buscar en la lista ordenada con la misma función de comparación. La función local ShowWhere muestra dónde está o debería estar el texto buscado:

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

Asegúrese siempre de usar el mismo tipo de comparación para la ordenación y la búsqueda. Si se usan distintos tipos de comparación para la ordenación y la búsqueda se producen resultados inesperados.

Las clases de colección como System.Collections.Hashtable, System.Collections.Generic.Dictionary<TKey,TValue> y System.Collections.Generic.List<T> tienen constructores que toman un parámetro System.StringComparer cuando el tipo de los elementos o claves es string. En general, debe usar estos constructores siempre que sea posible y especificar StringComparer.Ordinal u StringComparer.OrdinalIgnoreCase.

Vea también