Compartir a través de


Procedimientos recomendados para comparar cadenas en .NET

.NET proporciona una amplia compatibilidad con el desarrollo de aplicaciones localizadas y globalizadas, y facilita la aplicación de las convenciones de la referencia cultural actual o una referencia cultural específica al realizar operaciones comunes, como ordenar y mostrar cadenas. Pero ordenar o comparar cadenas no es siempre una operación dependiente de la referencia cultural. Por ejemplo, las cadenas usadas internamente por una aplicación normalmente deben gestionarse de manera idéntica en todas las culturas. Cuando los datos de cadena culturalmente independientes, como etiquetas XML, etiquetas HTML, nombres de usuario, rutas de acceso de archivo y nombres de objetos del sistema, se interpretan como si fueran sensibles a la referencia cultural, el código de aplicación puede estar sujeto a errores sutiles, un rendimiento deficiente y, en algunos casos, problemas de seguridad.

En este artículo, se examinan los métodos de ordenación, comparación y uso de mayúsculas y minúsculas de cadenas de .NET, se presentan recomendaciones para seleccionar un método adecuado de control de cadenas y se proporciona información adicional sobre los métodos de control de cadenas.

Recomendaciones sobre el uso de cadenas

Al desarrollar con .NET, siga estas recomendaciones al comparar cadenas.

Sugerencia

Varios métodos relacionados con cadenas realizan la comparación. Entre los ejemplos se incluyen String.Equals, String.Compare, String.IndexOfy String.StartsWith.

Evite lo siguiente cuando compare cadenas:

  • No emplee sobrecargas que no especifiquen explícita o implícitamente las reglas de comparación de cadenas para las operaciones de cadena.
  • No utilice las operaciones de cadena basadas en StringComparison.InvariantCulture en la mayoría de los casos. Una de las pocas excepciones es cuando se conservan datos lingüísticos pero culturalmente neutros.
  • No use una sobrecarga del String.Compare método o CompareTo y pruebe un valor devuelto de cero para determinar si dos cadenas son iguales.

Especificar comparaciones de cadenas explícitamente

La mayoría de los métodos de manipulación de cadenas de .NET están sobrecargados. Normalmente, una o varias sobrecargas aceptan la configuración predeterminada, mientras que otras no aceptan valores predeterminados y, en su lugar, definen la manera precisa en que se van a comparar o manipular las cadenas. La mayoría de los métodos que no confían en los valores predeterminados incluye un parámetro de tipo StringComparison, que es una enumeración que especifica explícitamente reglas para la comparación de cadenas por referencia cultural y uso de mayúsculas y minúsculas. En la siguiente tabla se describen los miembros de la enumeración StringComparison.

Miembro StringComparison Descripción
CurrentCulture Realiza una comparación con distinción entre mayúsculas y minúsculas usando la referencia cultural actual.
CurrentCultureIgnoreCase Realiza una comparación sin distinción entre mayúsculas y minúsculas usando la referencia cultural actual.
InvariantCulture Realiza una comparación con distinción entre mayúsculas y minúsculas usando la referencia cultural de todos los idiomas.
InvariantCultureIgnoreCase Realiza una comparación sin distinción entre mayúsculas y minúsculas usando la referencia cultural de todos los idiomas.
Ordinal Realiza una comparación ordinal.
OrdinalIgnoreCase Realiza una comparación ordinal sin distinción entre mayúsculas y minúsculas.

Por ejemplo, el IndexOf método , que devuelve el índice de una subcadena en un String objeto que coincide con un carácter o una cadena, tiene nueve sobrecargas:

Se recomienda seleccionar una sobrecarga que no use valores predeterminados, por los siguientes motivos:

  • Algunas sobrecargas con parámetros predeterminados (las que buscan un valor Char en la instancia de la cadena) realizan una comparación ordinal, mientras que otras (las que buscan una cadena en la instancia de la cadena) son dependientes de la referencia cultural. Es difícil recordar qué método usa el valor predeterminado y es fácil confundir las sobrecargas.

  • La intención del código que se basa en los valores predeterminados para las llamadas a métodos no está claro. En el ejemplo siguiente, en el cual se usan valores predeterminados, es difícil saber si el desarrollador pretendía realizar una comparación ordinal o lingüística de dos cadenas, o si había alguna diferencia al usar mayúsculas y minúsculas entre url.Scheme y "http" que pudiera hacer que la prueba de igualdad devolviera el valor false.

    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
    

En general, se recomienda llamar a un método que no se basa en los valores predeterminados, ya que hace que la intención del código sea inequívoca. Esto, a su vez, hace que el código sea más legible y fácil de depurar y mantener. En el ejemplo siguiente se abordan las preguntas planteadas sobre el ejemplo anterior. Indica claramente que se usa la comparación ordinal y que se omiten las diferencias en cuanto al uso de mayúsculas y minúsculas.

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

Detalles de la comparación de cadenas

La comparación de cadenas es el corazón de muchas operaciones relacionadas con cadenas, especialmente la ordenación y la comprobación de igualdad. Las cadenas se ordenan en un orden determinado: si "my" aparece antes de "string" en una lista ordenada de cadenas, "my" debe comparar menos o igual que "string". Además, la comparación define implícitamente la igualdad. La operación de comparación devuelve cero para las cadenas que considera iguales. Una buena interpretación es que ninguna cadena es menor que otra. Las operaciones más significativas que implican cadenas incluyen uno o ambos procedimientos: comparar con otra cadena y ejecutar una operación de ordenación bien definida.

Nota:

Puede descargar las tablas de ponderación de ordenación, un conjunto de archivos de texto que contienen información sobre los pesos de caracteres usados en operaciones de ordenación y comparación para sistemas operativos Windows, y la tabla de elementos de intercalación Unicode predeterminada, que es la versión más reciente de la tabla de ponderación de ordenación para Linux y macOS. La versión específica de la tabla de pesos de ordenación en Linux y macOS depende de la versión de las bibliotecas de componentes internacionales de Unicode instaladas en el sistema. Para obtener información sobre las versiones de ICU y las versiones unicode que implementan, consulte Descarga de ICU.

Sin embargo, la evaluación de dos cadenas para la igualdad o el criterio de ordenación no produce un único resultado correcto; el resultado depende de los criterios utilizados para comparar las cadenas. En especial, las comparaciones de cadenas que son ordinales o que se basan en las convenciones de ordenación y uso de mayúsculas y minúsculas de la referencia cultural actual o de la referencia cultural invariable (una referencia cultural válida para la configuración regional basada en el idioma inglés) pueden producir resultados diferentes.

Además, las comparaciones de cadenas que usan diferentes versiones de .NET o .NET en diferentes sistemas operativos o versiones del sistema operativo pueden devolver resultados diferentes. Para obtener más información, vea Cadenas y el estándar Unicode.

Comparaciones de cadenas que usan la referencia cultural actual

Un criterio implica usar las convenciones de la referencia cultural actual a la hora de comparar cadenas. Las comparaciones que se basan en la referencia cultural actual usan la referencia cultural o la configuración regional actual del subproceso. Si el usuario no establece la referencia cultural, el valor predeterminado es la configuración del sistema operativo. Siempre debe usar comparaciones basadas en la cultura actual cuando los datos son lingüísticamente relevantes y cuando refleje la interacción culturalmente sensible del usuario.

En cambio, el comportamiento de comparación y uso de mayúsculas y minúsculas de .NET cambia cuando la referencia cultural cambia. Esto sucede cuando una aplicación se ejecuta en un equipo que tiene una referencia cultural diferente del equipo en el que se desarrolló la aplicación o cuando el subproceso en ejecución cambia su referencia cultural. Este comportamiento es intencionado, pero sigue siendo no obvio para muchos desarrolladores. En el ejemplo siguiente se muestran las diferencias en el criterio de ordenación entre las culturas inglés de EE.UU. ("en-US") y sueco ("sv-SE"). Tenga en cuenta que las palabras "ångström", "Windows" y "Visual Studio" aparecen en diferentes posiciones de las matrices de cadenas ordenadas.

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

Las comparaciones sin distinción entre mayúsculas y minúsculas que usan la referencia cultural actual son iguales que las comparaciones dependientes de la referencia cultural, excepto que omiten el uso de mayúsculas y minúsculas según indica la referencia cultural actual del subproceso. Este comportamiento también puede manifestarse en órdenes de ordenación.

Las comparaciones que usan la semántica de referencia cultural actual son el valor predeterminado para los métodos siguientes:

En cualquier caso, se recomienda llamar a una sobrecarga que tenga un parámetro StringComparison para aclarar la intención de la llamada al método.

Los errores sutiles y no tan sutiles pueden surgir cuando los datos de cadena no lingüísticos se interpretan lingüísticamente, o cuando los datos de cadena de una referencia cultural determinada se interpretan mediante las convenciones de otra referencia cultural. El ejemplo canónico es el problema Turkish-I.

Para casi todos los alfabetos latinos, incluido el inglés estadounidense, el carácter "i" (\u0069) es la versión minúscula del carácter "I" (\u0049). Esta regla de mayúsculas y minúsculas se convierte rápidamente en el valor predeterminado para alguien que programe en esa referencia cultural. Sin embargo, el alfabeto turco ("tr-TR") incluye un carácter "I con un punto" "İ" (\u0130), que es la versión capital de "i". El turco también incluye un carácter "i sin punto" en minúscula, "ı" (\u0131), que en mayúsculas es "I". Este comportamiento también se produce en la cultura azerbaiyana ("az").

Por lo tanto, las suposiciones realizadas sobre poner en mayúscula la "i" o poner en minúscula la "I" no son válidas entre todas las culturas. Si usa las sobrecargas predeterminadas para las rutinas de comparación de cadenas, estarán sujetas a variaciones entre distintas referencias culturales. Si los datos que se van a comparar no son lingüísticos, el uso de las sobrecargas predeterminadas puede producir resultados no deseados, como se muestra en el siguiente intento de realizar una comparación sin distinción entre mayúsculas y minúsculas de las cadenas "bill" y "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

Esta comparación podría provocar problemas significativos si la cultura se usa inadvertidamente en entornos sensibles a la seguridad, como en el ejemplo siguiente. Una llamada de método como IsFileURI("file:") devuelve true si la referencia cultural actual es inglés de EE. UU., pero false si la referencia cultural actual es turca. Por lo tanto, en los sistemas turcos, alguien podría eludir las medidas de seguridad que bloquean el acceso a URI que no distinguen mayúsculas de minúsculas que comienzan por "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

En este caso, porque "file:" está destinado a interpretarse como un identificador no lingüístico insensible a factores culturales, el código debe escribirse como se muestra en el ejemplo siguiente.

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

Operaciones de cadenas ordinales

Especificar el StringComparison.Ordinal valor o StringComparison.OrdinalIgnoreCase en una llamada de método significa una comparación no lingüística en la que se omiten las características de los lenguajes naturales. Los métodos que se invocan con estos valores StringComparison basan las decisiones sobre las operaciones con cadenas en simples comparaciones de bytes en lugar de usos de mayúsculas y minúsculas o tablas de equivalencia parametrizadas por referencia cultural. En la mayoría de los casos, este enfoque se adapta mejor a la interpretación prevista de las cadenas, al tiempo que hace que el código sea más rápido y confiable.

Las comparaciones ordinales son comparaciones de cadenas en las que cada byte de cada cadena se compara sin interpretación lingüística; por ejemplo, "windows" no coincide con "Windows". Básicamente, se trata de una llamada a la función en tiempo de ejecución strcmp de C. Use esta comparación cuando el contexto indique que las cadenas deben coincidir exactamente o exija una directiva de coincidencia conservadora. Además, la comparación ordinal es la operación de comparación más rápida porque no aplica ninguna regla lingüística al determinar un resultado.

Las cadenas de .NET pueden contener caracteres NULL incrustados (y otros caracteres que no son de impresión). Una de las diferencias más claras entre la comparación ordinal y sensible a la referencia cultural (incluidas las comparaciones que usan la referencia cultural invariable) se refiere al control de caracteres NULL incrustados en una cadena. Estos caracteres se omiten cuando se utilizan los métodos String.Compare y String.Equals para realizar comparaciones que consideran la cultura (incluidas las comparaciones que utilizan la cultura invariable). Como resultado, las cadenas que contienen caracteres NULL incrustados se pueden considerar iguales a las cadenas que no lo hacen. Es posible que se omitan caracteres no imprimibles incrustados con el fin de los métodos de comparación de cadenas, como String.StartsWith.

Importante

Aunque los métodos de comparación de cadenas ignoran los caracteres NULL incrustados, los métodos de búsqueda de cadenas como String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf y String.StartsWith no.

En el ejemplo siguiente se realiza una comparación que distingue la referencia cultural de la cadena "Aa" con una cadena similar que contiene varios caracteres NULL incrustados entre "A" y "a", y se muestra cómo se consideran iguales las dos cadenas:

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

Sin embargo, las cadenas no se consideran iguales cuando se usa la comparación ordinal, como se muestra en el ejemplo siguiente:

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

Las comparaciones ordinales sin distinción entre mayúsculas y minúsculas son el siguiente enfoque más conservador. Estas comparaciones omiten la mayor parte del uso de mayúsculas y minúsculas; por ejemplo, "windows" coincide con "Windows". A la hora de tratar con caracteres ASCII, esta directiva es equivalente a StringComparison.Ordinal, salvo que omite el uso de mayúsculas y minúsculas habitual de ASCII. Por lo tanto, cualquier carácter de [A, Z] (\u0041-\u005A) coincide con el carácter correspondiente en [a,z] (\u0061-\007A). El uso de mayúsculas y minúsculas fuera del intervalo ASCII emplea las tablas de la referencia cultural de todos los idiomas. Por lo tanto, la siguiente comparación:

string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

es equivalente a (pero más rápido que) esta comparación:

string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)

Estas comparaciones siguen siendo muy rápidas.

Tanto StringComparison.Ordinal como StringComparison.OrdinalIgnoreCase usan los valores binarios directamente, y son más adecuados para la comparación. Cuando no esté seguro de la configuración de comparación, use uno de estos dos valores. Sin embargo, dado que realizan una comparación byte a byte, no ordenan por un ordenamiento lingüístico (como un diccionario inglés), sino por un ordenamiento binario. Los resultados pueden parecer extraños en la mayoría de los contextos si se muestran a los usuarios.

La semántica ordinal es el valor predeterminado para las sobrecargas de String.Equals que no incluyen un argumento StringComparison (incluyendo el operador de igualdad). En cualquier caso, se recomienda llamar a una sobrecarga que tenga un parámetro StringComparison .

Operaciones de cadenas que usan la referencia cultural de todos los idiomas

Las comparaciones con la cultura invariable usan la propiedad CompareInfo devuelta por la propiedad estática CultureInfo.InvariantCulture. Este comportamiento es el mismo en todos los sistemas; traduce cualquier carácter fuera de su intervalo en lo que cree que son caracteres invariables equivalentes. Esta política puede ser útil para mantener un comportamiento uniforme de cadenas entre culturas, pero a menudo genera resultados inesperados.

Las comparaciones sin distinción entre mayúsculas y minúsculas con la referencia cultural de todos los idiomas usan también la propiedad estática CompareInfo devuelta por la propiedad estática CultureInfo.InvariantCulture para obtener información de comparación. Cualquier diferencia en el uso de mayúsculas y minúsculas entre estos caracteres traducidos se pasa por alto.

Comparaciones que usan StringComparison.InvariantCulture y StringComparison.Ordinal funcionan de forma idéntica en cadenas ASCII. Sin embargo, StringComparison.InvariantCulture toma decisiones lingüísticas que podrían no ser adecuadas para las cadenas que deben interpretarse como un conjunto de bytes. El CultureInfo.InvariantCulture.CompareInfo objeto hace que el Compare método interprete determinados conjuntos de caracteres como equivalentes. Por ejemplo, la equivalencia siguiente es válida en la referencia cultural invariable:

InvariantCulture: a + ̊ = å

El carácter LETRA LATINA A MINÚSCULA "a" (\u0061), cuando está junto al carácter ANILLO SUPERIOR COMBINABLE "+ " ̊" (\u030a), se interpreta como el carácter LETRA LATINA MINÚSCULA A CON ANILLO SUPERIOR "å" (\u00e5). Como se muestra en el ejemplo siguiente, este comportamiento difiere de la comparación 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

Al interpretar nombres de archivo, cookies o cualquier otra cosa en la que pueda aparecer una combinación como "å", las comparaciones ordinales siguen ofreciendo el comportamiento más transparente y adecuado.

En conjunto, la referencia cultural de todos los idiomas tiene pocas propiedades que la hagan útil para la comparación. Realiza la comparación de una manera lingüísticamente relevante, lo que impide que garantice una equivalencia simbólica completa, pero no es la opción para mostrarse en ninguna cultura. Una de las pocas razones para usar StringComparison.InvariantCulture para la comparación es conservar los datos ordenados para una presentación entre culturas idéntica. Por ejemplo, si un archivo de datos grande que contiene una lista de identificadores ordenados para mostrar acompaña a una aplicación, agregar a esta lista requeriría una inserción con ordenación de estilo invariable.

Elección de un miembro de StringComparison para la invocación del método

En la siguiente tabla se describe el mapeo de un contexto de cadena semántica a un miembro de enumeración StringComparison.

Datos Comportamiento Valor de System.StringComparison

valor
Identificadores internos con distinción entre mayúsculas y minúsculas.

Identificadores con distinción entre mayúsculas y minúsculas en estándares como XML y HTTP.

Configuraciones relacionadas con la seguridad con distinción entre mayúsculas y minúsculas.
Identificador no lingüístico, donde los bytes coinciden exactamente. Ordinal
Identificadores internos sin distinción entre mayúsculas y minúsculas.

Identificadores sin distinción entre mayúsculas y minúsculas en estándares como XML y HTTP.

Rutas de acceso a archivos.

Claves y valores del Registro.

Variables de entorno.

Identificadores de recursos (por ejemplo, nombres de identificadores).

Configuraciones relacionadas con la seguridad sin distinción entre mayúsculas y minúsculas.
Identificador no lingüístico, donde el caso es irrelevante. OrdinalIgnoreCase
Algunos datos almacenados lingüísticamente pertinentes.

Visualización de datos lingüísticos que requieren un criterio de ordenación fijo.
Datos independientes culturalmente que siguen siendo lingüísticomente relevantes. InvariantCulture

-o-

InvariantCultureIgnoreCase
Datos que se muestran al usuario.

La mayoría de los datos proporcionados por el usuario.
Datos que requieren costumbres lingüísticas locales. CurrentCulture

-o-

CurrentCultureIgnoreCase

Métodos de comparación de cadenas comunes en .NET

En las secciones siguientes se describen los métodos que se usan con más frecuencia para la comparación de cadenas.

String.Compare

Interpretación predeterminada: StringComparison.CurrentCulture.

Al ser la operación fundamental para la interpretación de cadenas, todas las instancias de estas llamadas al método se deben examinar para determinar si las cadenas se deben interpretar según la referencia cultural actual o se deben separar de la referencia cultural (simbólicamente). Normalmente, se trata del último caso y se debe usar en su lugar una comparación StringComparison.Ordinal.

La clase System.Globalization.CompareInfo , devuelta por la propiedad CultureInfo.CompareInfo , también incluye un método Compare que proporciona un gran número de opciones de coincidencia (ordinal, omitir el espacio en blanco, omitir el tipo de kana, etc.) por medio de la enumeración de marca CompareOptions .

String.CompareTo

Interpretación predeterminada: StringComparison.CurrentCulture.

Este método no ofrece actualmente una sobrecarga que especifique un tipo StringComparison. Normalmente es posible convertir este método al formulario recomendado String.Compare(String, String, StringComparison) .

Los tipos que implementan las IComparable interfaces e IComparable<T> implementan este método. Dado que no ofrece la opción de un StringComparison parámetro, la implementación de tipos a menudo permite al usuario especificar un StringComparer en su constructor. En el ejemplo siguiente se define una FileName clase cuyo constructor de clase incluye un StringComparer parámetro . A continuación, este StringComparer objeto se usa en el 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

Interpretación predeterminada: StringComparison.Ordinal.

La clase String le permite comprobar la igualdad llamando a las sobrecargas de método estático o de instancia Equals , o usando el operador de igualdad estático. Las sobrecargas y el operador usan la comparación ordinal de forma predeterminada. Sin embargo, todavía sigue siendo recomendable llamar a una sobrecarga que especifique explícitamente el tipo StringComparison aunque desee realizar una comparación ordinal; esto facilita la búsqueda de cierta interpretación de la cadena en el código.

String.ToUpper y String.ToLower

Interpretación predeterminada: StringComparison.CurrentCulture.

Debe tener cuidado al usar estos métodos String.ToUpper() y String.ToLower(), ya que forzar que una cadena esté en mayúsculas o en minúsculas se usa a menudo como una pequeña normalización para comparar cadenas independientemente del uso de mayúsculas y minúsculas. En tal caso, considere la posibilidad de emplear una comparación sin distinción entre mayúsculas y minúsculas.

Los String.ToUpperInvariant métodos y String.ToLowerInvariant también están disponibles. ToUpperInvariant es la manera estándar de normalizar el caso. Las comparaciones realizadas mediante StringComparison.OrdinalIgnoreCase se comportan como la composición de dos llamadas: llamar a ToUpperInvariant en ambos argumentos de cadena y realizar una comparación mediante StringComparison.Ordinal.

También hay sobrecargas para convertir a mayúsculas y minúsculas en una referencia cultural concreta, pasando al método un objeto CultureInfo que representa esa referencia cultural.

Char.ToUpper y Char.ToLower

Interpretación predeterminada: StringComparison.CurrentCulture.

Los Char.ToUpper(Char) métodos y Char.ToLower(Char) funcionan de forma similar a los String.ToUpper() métodos y String.ToLower() descritos en la sección anterior.

String.StartsWith y String.EndsWith

Interpretación predeterminada: StringComparison.CurrentCulture.

De forma predeterminada, ambos métodos realizan una comparación sensible a la cultura. En concreto, pueden pasar por alto los caracteres que no son de impresión.

String.IndexOf y String.LastIndexOf

Interpretación predeterminada: StringComparison.CurrentCulture.

Hay una falta de coherencia en la forma en que las sobrecargas predeterminadas de estos métodos realizan comparaciones. Todos los métodos String.IndexOf y String.LastIndexOf que incluyen un parámetro Char realizan una comparación ordinal, pero los métodos predeterminados String.IndexOf y String.LastIndexOf que incluyen un parámetro String realizan una comparación sensible a la referencia cultural.

Si llama al método String.IndexOf(String) o String.LastIndexOf(String) y le pasa una cadena para ubicar en la instancia actual, se recomienda llamar a una sobrecarga que especifique explícitamente el tipo StringComparison . Las sobrecargas que incluyen un Char argumento no permiten especificar un StringComparison tipo.

Métodos que realizan la comparación de cadenas indirectamente

Algunos métodos que no son de cadena que tienen la comparación de cadenas como una operación central usan el StringComparer tipo . La StringComparer clase incluye seis propiedades estáticas que devuelven StringComparer instancias cuyos StringComparer.Compare métodos realizan los siguientes tipos de comparaciones de cadenas:

Array.Sort y Array.BinarySearch

Interpretación predeterminada: StringComparison.CurrentCulture.

Cuando se almacenan datos en una colección, o cuando se leen datos almacenados de un archivo o una base de datos en una colección, el cambio de la referencia cultural actual puede invalidar los valores invariables de la colección. El Array.BinarySearch método supone que los elementos de la matriz que se van a buscar ya están ordenados. Para ordenar cualquier elemento de cadena de la matriz, el Array.Sort método llama al String.Compare método para ordenar elementos individuales. El uso de un comparador sensible a la referencia cultural puede ser peligroso si la referencia cultural cambia entre el momento en que se ordena la matriz y se busca su contenido. Por ejemplo, en el código siguiente, el almacenamiento y la recuperación funcionan en el comparador proporcionado implícitamente por la Thread.CurrentThread.CurrentCulture propiedad . Si la referencia cultural puede cambiar entre las llamadas a StoreNames y DoesNameExist, y especialmente si el contenido de la matriz se conserva en alguna parte entre las dos llamadas al método, se puede producir un error en la búsqueda binaria.

// 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

Aparece una variación recomendada en el ejemplo siguiente, que usa el mismo método de comparación ordinal (que no distingue la referencia cultural) para ordenar y buscar en la matriz. El código de cambio se refleja en las líneas etiquetadas Line A y Line B en los dos ejemplos.

// 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

Si estos datos se conservan y se trasladan entre culturas, y la ordenación se usa para presentar estos datos al usuario, puede considerar el uso de StringComparison.InvariantCulture, que opera lingüísticamente para mejorar la presentación de datos al usuario, pero no se ve afectado por los cambios en la cultura. En el ejemplo siguiente se modifican los dos ejemplos anteriores para usar la cultura invariable para ordenar y buscar en la 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

Ejemplo de colecciones: Constructor de tablas hash

Las cadenas hash proporcionan un segundo ejemplo de una operación que se ve afectada por la forma en que se comparan las cadenas.

En el ejemplo siguiente se crea una instancia de un objeto Hashtable pasándole el objeto StringComparer devuelto por la propiedad StringComparer.OrdinalIgnoreCase . Dado que una clase StringComparer derivada de StringComparer implementa la IEqualityComparer interfaz, su GetHashCode método se usa para calcular el código hash de las cadenas en la tabla 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

Consulte también