Procedure consigliate per il confronto delle stringhe in .NET

.NET offre un ampio supporto per lo sviluppo di applicazioni localizzate e globalizzate e semplifica l'applicazione delle convenzioni relative alle impostazioni cultura correnti o alle impostazioni cultura specifiche quando si eseguono operazioni comuni come l'ordinamento e la visualizzazione delle stringhe. Tuttavia, l'ordinamento o il confronto delle stringhe non è sempre un'operazione con distinzione delle impostazioni cultura. Ad esempio, le stringhe usate internamente da un'applicazione in genere devono essere gestite in modo identico in tutte le impostazioni cultura. Quando i dati di stringa indipendenti dalle impostazioni cultura, ad esempio i tag XML, i tag HTML, i nomi utente, i percorsi di file e i nomi degli oggetti di sistema, vengono interpretati come dati con distinzione delle impostazioni cultura, nel codice dell'applicazione possono verificarsi bug complessi, riduzioni delle prestazioni e, in alcuni casi, problemi di sicurezza.

Questo articolo esamina i metodi di ordinamento, confronto e utilizzo di maiuscole e minuscole nelle stringhe in .NET, offre delle raccomandazioni per selezionare il metodo di gestione delle stringhe appropriato e fornisce informazioni aggiuntive sui metodi di gestione delle stringhe.

Suggerimenti per l'uso delle stringhe

Quando si sviluppa con .NET, seguire queste semplici raccomandazioni quando si confrontano stringhe:

Evitare le procedure seguenti quando si confrontano stringhe:

  • Non usare overload che non specificano in modo esplicito o implicito le regole di confronto tra stringhe per le operazioni di stringa.
  • Non usare operazioni di stringa basate su StringComparison.InvariantCulture nella maggior parte dei casi. Una delle poche eccezioni riguarda la conservazione di dati significativi a livello linguistico, ma indipendenti dalle impostazioni cultura.
  • Non usare un overload del metodo String.Compare o CompareTo ed eseguire un test per il valore restituito zero per determinare se due stringhe sono uguali.

Specifica esplicita per il confronto tra stringhe

Molti dei metodi di modifica delle stringhe in .NET sono di tipo overload. In genere, uno o più overload accettano le impostazioni predefinite, mentre altri accettano le impostazioni non predefinite, specificando invece una determinata procedura di confronto o modifica delle stringhe. La maggior parte dei metodi che non si basano sulle impostazioni predefinite include un parametro di tipo StringComparison, che corrisponde a un'enumerazione che specifica in modo esplicito le regole per il confronto tra stringhe in base alle impostazioni cultura e alle maiuscole e minuscole. La tabella seguente descrive i membri dell'enumerazione StringComparison .

Membro StringComparison Descrizione
CurrentCulture Esegue un confronto con distinzione tra maiuscole e minuscole usando le impostazioni cultura correnti.
CurrentCultureIgnoreCase Esegue un confronto senza distinzione tra maiuscole e minuscole usando le impostazioni cultura correnti.
InvariantCulture Esegue un confronto con distinzione tra maiuscole e minuscole usando la lingua inglese.
InvariantCultureIgnoreCase Esegue un confronto senza distinzione tra maiuscole e minuscole usando la lingua inglese.
Ordinal Esegue un confronto ordinale.
OrdinalIgnoreCase Esegue un confronto ordinale senza distinzione tra maiuscole e minuscole.

Ad esempio, il metodo IndexOf , che restituisce l'indice di una sottostringa in un oggetto String che corrisponde a un carattere o a una stringa, ha nove overload:

Si consiglia di selezione un overload che non uso i valori predefiniti, per i seguenti motivi:

  • Alcuni overload con parametri predefiniti (quelli che cercano Char nell'istanza della stringa) eseguono un confronto ordinale, mentre altri (quelli che cercano una stringa nell'istanza della stringa) applicano la distinzione delle impostazioni cultura. È difficile ricordare quale valore predefinito viene usato dai diversi metodi ed è facile confondere gli overload.

  • Lo scopo del codice basato sui valori predefiniti per le chiamate al metodo non è chiaro. Nell'esempio seguente, che si basa su impostazioni predefinite, è difficile capire se lo sviluppatore intendeva eseguire un confronto ordinale o linguistico tra due stringhe o se la differenza tra maiuscole e minuscole tra protocol e "http" può causare la restituzione di false.

    string protocol = GetProtocol(url);
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    
    Dim protocol As String = GetProtocol(url)
    If String.Equals(protocol, "http") Then
        ' ...Code to handle HTTP protocol.
    Else
        Throw New InvalidOperationException()
    End If
    

In generale, si consiglia di chiamare un metodo non basato sulle impostazioni predefinite perché disambigua lo scopo del codice. In questo modo, anche il codice diventa più leggibile ed è più facile eseguirne il debug e la manutenzione. L'esempio seguente riguarda le domande relative all'esempio precedente. Viene specificato che viene utilizzato il confronto ordinale e che le differenze tra maiuscole e minuscole vengono ignorate.

string protocol = GetProtocol(url);
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}
Dim protocol As String = GetProtocol(url)
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
    ' ...Code to handle HTTP protocol.
Else
    Throw New InvalidOperationException()
End If

Dettagli sul confronto tra stringhe

Il confronto tra stringhe è la base di molte operazioni relative alle stringhe, in particolare l'ordinamento e il test di uguaglianza. L'ordinamento delle stringhe viene eseguito in un modo specifico: se "my" compare prima di "string" in un elenco ordinato di stringhe, nel confronto "my" deve essere minore o uguale a "string". Inoltre, il confronto definisce implicitamente l'uguaglianza. L'operazione di confronto restituisce zero per le stringhe che considera uguali. In altre parole, nessuna stringa viene considerata minore delle altre. Le operazioni più significative relative alle stringhe includono una o più delle seguenti procedure: confronto con un'altra stringa ed esecuzione di un'operazione di ordinamento definita correttamente.

Nota

È possibile scaricare le tabelle di ordinamento spessore, un set di file di testo che contengono informazioni sugli spessori dei caratteri usati nelle operazioni di ordinamento e confronto per i sistemi operativi Windows, e la tabella degli elementi delle regole di confronto Unicode predefinite, la versione più recente della tabella di ordinamento spessore per Linux e MacOS. La versione specifica della tabella di ordinamento spessore in Linux e macOS dipende dalla versione delle librerie International Components for Unicode installate nel sistema. Per informazioni sulle versioni ICU e sulle versioni Unicode implementate, vedere Downloading ICU (Download di ICU).

Tuttavia, la valutazione di due stringhe per l'uguaglianza e l'ordinamento non produce un unico risultato corretto; l'esito dipende dai criteri usati per il confronto delle stringhe. In particolare, i confronti tra stringhe ordinali o basati sulle convenzioni di uso di maiuscole e minuscole e di ordinamento delle impostazioni cultura correnti o delle impostazioni cultura inglese non dipendenti da paese/area geografica possono produrre risultati diversi.

I confronti di stringhe tramite versioni diverse di .NET oppure tramite .NET in sistemi operativi o versioni del sistema operativo diverse possono inoltre restituire risultati diversi. Per altre informazioni, vedere Stringhe e standard Unicode.

Confronti tra stringhe che usano le impostazioni cultura correnti

Un criterio prevede l'uso delle convenzioni delle impostazioni cultura correnti quando si confrontano le stringhe. I confronti basati sulle impostazioni cultura correnti usano le impostazioni cultura o le impostazioni locali correnti del thread. Se le impostazioni cultura non vengono impostate dall'utente, vengono usate le impostazioni predefinite della finestra Opzioni internazionali nel Pannello di controllo. È necessario usare sempre i confronti basati sulle impostazioni cultura correnti quando i dati sono linguisticamente rilevanti e quando riflettono un'interazione utente con distinzione delle impostazioni cultura.

Tuttavia, il comportamento di confronto e di utilizzo di maiuscole e minuscole in .NET cambia quando vengono modificate le impostazioni cultura. Ciò accade quando un'applicazione viene eseguita in un computer con impostazioni cultura diverse da quelle del computer in cui è stata sviluppata oppure quando il thread di esecuzione modifica le proprie impostazioni cultura. Questo comportamento è intenzionale, tuttavia resta poco chiaro per molti sviluppatori. L'esempio seguente illustra le differenze nell'ordinamento tra le impostazioni cultura della lingua inglese per gli Stati Uniti ("en-US") e di quella svedese ("sv-SE"). Si noti che le parole "ångström", "Windows" e "Visual Studio" vengono visualizzate in posizioni diverse nelle matrici di stringhe ordinate.

using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string[] values= { "able", "ångström", "apple", "Æble",
                         "Windows", "Visual Studio" };
      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);
    }

    private static void DisplayArray(string[] values)
    {
      Console.WriteLine("Sorting using the {0} culture:",
                        CultureInfo.CurrentCulture.Name);
      foreach (string value in values)
         Console.WriteLine("   {0}", 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
//          Æble
//          apple
//          Windows
//          Visual Studio
//          ångström
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim values() As String = {"able", "ångström", "apple", _
                                   "Æble", "Windows", "Visual Studio"}
        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

    Private Sub DisplayArray(values() As String)
        Console.WRiteLine("Sorting using the {0} culture:", _
                          CultureInfo.CurrentCulture.Name)
        For Each value As String In values
            Console.WriteLine("   {0}", 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
'          Æble
'          apple
'          Windows
'          Visual Studio
'          ångström

I confronti senza distinzione tra maiuscole e minuscole che usano le impostazioni cultura correnti sono uguali a quelli con distinzione delle impostazioni cultura, ma ignorano la distinzione tra maiuscole e minuscole come indicato dalle impostazioni cultura correnti del thread. Questo comportamento può manifestarsi anche negli ordinamenti.

I confronti che usano la semantica delle impostazioni cultura correnti sono i confronti predefiniti per i seguenti metodi:

In ogni caso, si consiglia di chiamare un overload con il parametro StringComparison per rendere chiaro lo scopo della chiamata al metodo.

È possibile che vengano generati bug complessi e meno complessi quando i dati non linguistici della stringa vengono interpretati linguisticamente oppure quando i dati della stringa di specifiche impostazioni cultura vengono interpretati usando le convenzioni di altre impostazioni cultura. L'esempio canonico è il problema della I turca.

Per quasi tutti gli alfabeti latini, incluso l'inglese (Stati Uniti), il carattere "i" (\u0069) corrisponde alla versione minuscola del carattere "I" (\u0049). Questa regola di utilizzo di maiuscole e minuscole diventa rapidamente l'impostazione predefinita per chi programma queste impostazioni cultura. Tuttavia, l'alfabeto turco ("tr-TR") include una "I con punto", "İ" (\u0130), che è la versione maiuscola di "i". In turco esiste anche un carattere minuscolo "i senza punto", "ı" (\u0131), la cui versione maiuscola è "I". Questo comportamento si verifica anche con le impostazioni cultura azerbaigiana ("az").

Pertanto, i presupposti relativi all'uso della maiuscola per "i" o della minuscola per "I" non sono validi in tutte le impostazioni cultura. Se si usano gli overload predefiniti per le routine di confronto tra stringhe, questi saranno soggetti a variazioni tra le diverse impostazioni cultura. Se i dati da confrontare sono di tipo non linguistico, l'uso degli overload predefiniti può produrre risultati indesiderati, come illustrato in questo tentativo di eseguire un confronto senza distinzione tra maiuscole e minuscole delle stringhe "file" e "FILE".

using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string fileUrl = "file";
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}",
                       fileUrl.StartsWith("FILE", true, null));
      Console.WriteLine();

      Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}",
                        fileUrl.StartsWith("FILE", true, null));
   }
}
// The example displays the following output:
//       Culture = English (United States)
//       (file == FILE) = True
//
//       Culture = Turkish (Turkey)
//       (file == FILE) = False
Imports System.Globalization
Imports System.Threading

Module Example
    Public Sub Main()
        Dim fileUrl = "file"
        Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Console.WriteLine("Culture = {0}", _
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        Console.WriteLine("(file == FILE) = {0}", _
                         fileUrl.StartsWith("FILE", True, Nothing))
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
        Console.WriteLine("Culture = {0}", _
                          Thread.CurrentThread.CurrentCulture.DisplayName)
        Console.WriteLine("(file == FILE) = {0}", _
                          fileUrl.StartsWith("FILE", True, Nothing))
    End Sub
End Module
' The example displays the following output:
'       Culture = English (United States)
'       (file == FILE) = True
'       
'       Culture = Turkish (Turkey)
'       (file == FILE) = False

Questo confronto può causare problemi significativi se le impostazioni cultura vengono usate inavvertitamente in impostazioni relative alla sicurezza, come nel seguente esempio. Una chiamata al metodo come IsFileURI("file:") restituisce true , se le impostazioni cultura correnti sono per la lingua inglese (Stati Uniti) oppure false se le impostazioni cultura correnti sono per la lingua turca. Quindi, nei sistemi turchi, qualcuno potrebbe aggirare le misure di sicurezza che bloccano l'accesso agli URI senza distinzione tra maiuscole e minuscole che iniziano con "FILE:".

public static bool IsFileURI(String path)
{
   return path.StartsWith("FILE:", true, null);
}
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", True, Nothing)
End Function

In questo caso, poiché "file:" deve essere interpretato come identificatore non linguistico, non linguistico, il codice deve essere invece scritto come illustrato nell'esempio seguente:

public static bool IsFileURI(string path)
{
   return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

Operazioni di stringa ordinale

La specifica del valore StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase in una chiamata al metodo indica un confronto non linguistico in cui le funzionalità dei linguaggi naturali vengono ignorate. I metodi richiamati con questi valori StringComparison basano le decisioni relative alle operazioni di stringa su confronti di byte semplici invece che sull'utilizzo di maiuscole e minuscole o di tabelle di equivalenza parametrizzate dalle impostazioni cultura. In molti casi, questo approccio risulta più adatto per l'interpretazione desiderata delle stringhe e rende il codice più veloce e affidabile.

I confronti ordinali sono confronti tra stringhe in cui ogni byte di ogni stringa viene confrontato senza interpretazione linguistica, ad esempio "windows" non corrisponde a "Windows". Si tratta essenzialmente di una chiamata alla funzione strcmp di C Runtime. Usare questo confronto quando il contesto impone l'esatta corrispondenza delle stringhe o richiede un criterio di corrispondenza conservativo. Inoltre, il confronto ordinale è l'operazione di confronto più rapida perché non applica regole linguistiche quando determina un risultato.

Le stringhe in .NET possono contenere caratteri null incorporati. Una delle differenze più evidenti tra il confronto ordinale e quello con distinzione delle impostazioni cultura (inclusi i confronti che usano la lingua inglese) riguarda la gestione dei caratteri null incorporati in una stringa. Questi caratteri vengono ignorati quando si usano i metodi String.Compare e String.Equals per eseguire confronti con distinzione delle impostazioni cultura (inclusi i confronti che usano la lingua inglese). Di conseguenza, nei confronti con distinzione delle impostazioni cultura, le stringhe che contengono caratteri null incorporati e quelle che non li contengono possono essere considerate uguali.

Importante

Anche se i metodi di confronto tra stringhe ignorano i caratteri null incorporati, i metodi di ricerca di stringhe come String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfe String.StartsWith li prendono in considerazione.

Nell'esempio seguente viene eseguito un confronto con distinzione tra impostazioni cultura della stringa "Aa" con una stringa simile che contiene diversi caratteri Null incorporati tra "A" e "a" e mostra come vengono considerate uguali le due stringhe:

using System;

public class Example
{
   public static void Main()
   {
      string str1 = "Aa";
      string str2 = "A" + new String('\u0000', 3) + "a";
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
                        str1, ShowBytes(str1), str2, ShowBytes(str2));
      Console.WriteLine("   With String.Compare:");
      Console.WriteLine("      Current Culture: {0}",
                        String.Compare(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}",
                        String.Compare(str1, str2, StringComparison.InvariantCulture));

      Console.WriteLine("   With String.Equals:");
      Console.WriteLine("      Current Culture: {0}",
                        String.Equals(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}",
                        String.Equals(str1, str2, StringComparison.InvariantCulture));
   }

   private static string ShowBytes(string str)
   {
      string hexString = String.Empty;
      for (int ctr = 0; ctr < str.Length; ctr++)
      {
         string result = String.Empty;
         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:
//          Current Culture: 0
//          Invariant Culture: 0
//       With String.Equals:
//          Current Culture: True
//          Invariant Culture: True
Module Example
    Public Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
        Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                          str1, ShowBytes(str1), str2, ShowBytes(str2))
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine("      Current Culture: {0}", _
                          String.Compare(str1, str2, StringComparison.CurrentCulture))
        Console.WriteLine("      Invariant Culture: {0}", _
                          String.Compare(str1, str2, StringComparison.InvariantCulture))

        Console.WriteLine("   With String.Equals:")
        Console.WriteLine("      Current Culture: {0}", _
                          String.Equals(str1, str2, StringComparison.CurrentCulture))
        Console.WriteLine("      Invariant Culture: {0}", _
                          String.Equals(str1, str2, StringComparison.InvariantCulture))
    End Sub

    Private 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 = String.Empty
            result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2)
            hexString += result
        Next
        Return hexString.Trim()
    End Function
End Module

Tuttavia, le stringhe non vengono considerate uguali quando si usa il confronto ordinale, come illustrato nell'esempio seguente:

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
                  str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine("   With String.Compare:");
Console.WriteLine("      Ordinal: {0}",
                  String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine("   With String.Equals:");
Console.WriteLine("      Ordinal: {0}",
                  String.Equals(str1, str2, StringComparison.Ordinal));
// 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
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                  str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine("   With String.Compare:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine("   With String.Equals:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Equals(str1, str2, StringComparison.Ordinal))
' 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

I confronti ordinali senza distinzione tra maiuscole e minuscole rappresentano il secondo approccio più conservativo. Questi confronti ignorano quasi del tutto l'utilizzo di maiuscole e minuscole, ad esempio "windows" corrisponde a "Windows". Quando si usano i caratteri ASCII, questo criterio è equivalente a StringComparison.Ordinal, ma ignora il normale utilizzo di maiuscole e minuscole ASCII. Quindi, qualsiasi carattere in [A, Z] (\u0041-\u005A) corrisponde al carattere corrispondente in [a,z] (\u0061-\007A). L'utilizzo di maiuscole e minuscole al di fuori dell'intervallo ASCII usa le tabelle in lingua inglese. Pertanto, il confronto seguente:

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

è equivalente (ma più rapido) rispetto al confronto:

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

Questi confronti sono comunque molto rapidi.

StringComparison.Ordinal e StringComparison.OrdinalIgnoreCase usano direttamente i valori binari e sono i più adatti per la corrispondenza. Se non si è sicuri delle impostazioni di confronto, usare uno di questi due valori. Tuttavia, poiché eseguono un confronto byte per byte, l'ordinamento non viene eseguito linguisticamente (come in un dizionario inglese), ma in modo binario. Se visualizzati dagli utenti, i risultati possono sembrare strani in molti contesti.

La semantica ordinale è l'impostazione predefinita per gli overload String.Equals che non includono un argomento StringComparison (incluso l'operatore di uguaglianza). In ogni caso, si consiglia di chiamare un overload con un parametro StringComparison .

Operazioni di stringa che usano impostazioni cultura non dipendenti da paese/area geografica

I confronti con la lingua inglese usano la proprietà CompareInfo restituita dalla proprietà statica CultureInfo.InvariantCulture . Questo comportamento è uguale in tutti i sistemi. Converte i caratteri al di fuori dell'intervallo in quelli che considera caratteri equivalenti in lingua inglese. Questo criterio può essere utile per gestire il comportamento di un set di stringhe nelle impostazioni cultura, ma spesso produce risultati imprevisti.

I confronti senza distinzione tra maiuscole e minuscole con la lingua inglese usano la proprietà statica CompareInfo restituita dalla proprietà statica CultureInfo.InvariantCulture anche per le informazioni sul confronto. Le differenze tra maiuscole e minuscole in questi caratteri convertiti vengono ignorate.

I confronti che usano StringComparison.InvariantCulture e StringComparison.Ordinal funzionano in modo identico nelle stringhe ASCII. Tuttavia, StringComparison.InvariantCulture prende decisioni linguistiche che potrebbero non essere appropriate per le stringhe che devono essere interpretate come set di byte. L'oggetto CultureInfo.InvariantCulture.CompareInfo fa sì che il metodo Compare interpreti alcuni set di caratteri come equivalenti. Ad esempio, la seguente equivalenza è valida in lingua inglese:

InvariantCulture: a + ̊ = å

La LETTERA PICCOLA LATINA Un carattere "a" (\u0061), quando si trova accanto al carattere DI COMBINAZIONE ANELLO SOPRA "+ " ̊" (\u030a), viene interpretato come la LETTERA PICCOLA LATINA A CON ANELLO SOPRA carattere "å" (\u00e5). Come mostrato nell'esempio seguente, questo comportamento è diverso dal confronto ordinale.

string separated = "\u0061\u030a";
string combined = "\u00e5";

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

Quando si interpretano i nomi di file, i cookie o qualsiasi altro elemento che può contenere un carattere come "å", i confronti ordinali offrono il comportamento più trasparente e appropriato.

In realtà la lingua inglese ha poche proprietà che la rendono utile per il confronto. Esegue il confronto in un modo linguisticamente rilevante, che non assicura un'equivalenza simbolica completa, ma non rappresenta la scelta ottimale per la visualizzazione nelle impostazioni cultura. Uno dei pochi motivi per usare StringComparison.InvariantCulture per il confronto riguarda la possibilità di conservare i dati ordinati per visualizzarli in modo identico nelle varie impostazioni cultura. Ad esempio, se un file di dati di grandi dimensioni che contiene un elenco di identificatori ordinati per la visualizzazione viene associato a un'applicazione, per aggiungere elementi all'elenco è necessario l'inserimento con un ordinamento in lingua inglese.

Scelta di un membro StringComparison per la chiamata al metodo

Nella tabella seguente viene descritto il mapping dal contesto della stringa semantica a un StringComparison membro di enumerazione:

Data Comportamento System.StringComparison corrispondente

Valore
Identificatori interni con distinzione tra maiuscole e minuscole.

Identificatori con distinzione tra maiuscole e minuscole in standard come XML e HTTP.

Impostazioni relative alla sicurezza con distinzione tra maiuscole e minuscole.
Identificatore non linguistico, con una corrispondenza esatta dei byte. Ordinal
Identificatori interni senza distinzione tra maiuscole e minuscole.

Identificatori senza distinzione tra maiuscole e minuscole in standard come XML e HTTP.

Percorsi di file.

Chiavi e valori del Registro di sistema.

Variabili di ambiente.

Identificatori di risorse (ad esempio, nomi di handle).

Impostazioni relative alla sicurezza senza distinzione tra maiuscole e minuscole.
Identificatore non linguistico, in cui la distinzione tra maiuscole e minuscole non è rilevante. In particolare, dati archiviati nella maggior parte dei servizi di sistema Windows. OrdinalIgnoreCase
Alcuni dati persistenti e linguisticamente rilevanti.

Visualizzazione di dati linguistici che richiedono un ordinamento fisso.
Dati indipendenti dalle impostazioni cultura, ma ancora linguisticamente rilevanti. InvariantCulture

-oppure-

InvariantCultureIgnoreCase
Dati visualizzati dall'utente.

La maggior parte dell'input utente.
Dati che richiedono personalizzazioni linguistiche locali. CurrentCulture

-oppure-

CurrentCultureIgnoreCase

Metodi comuni di confronto tra stringhe in .NET

Le sezioni seguenti descrivono i metodi più comuni per il confronto tra stringhe.

String.Compare

Interpretazione predefinita: StringComparison.CurrentCulture.

Poiché si tratta dell'operazione più importante per l'interpretazione della stringa, tutte le istanze di queste chiamate al metodo devono essere esaminate per determinare se le stringhe devono essere interpretate in base alle impostazioni cultura correnti o devono essere dissociate dalle impostazioni cultura (simbolicamente). In genere, si tratta del secondo caso e deve pertanto essere utilizzato un confronto StringComparison.Ordinal .

La classe System.Globalization.CompareInfo , restituita dalla proprietà CultureInfo.CompareInfo , include anche un metodo Compare che fornisce numerose opzioni di corrispondenza (ordinale, con esclusione degli spazi vuoti, con esclusione del tipo Kana e così via) mediante l'enumerazione flag CompareOptions .

String.CompareTo

Interpretazione predefinita: StringComparison.CurrentCulture.

Questo metodo attualmente non offre un overload che specifica un tipo StringComparison . In genere è possibile convertire questo metodo nel formato String.Compare(String, String, StringComparison) consigliato.

I tipi che implementano le interfacce IComparable e IComparable<T> implementano questo metodo. Poiché non offre l'opzione di un parametro StringComparison , l'implementazione dei tipi spesso lascia specificare all'utente StringComparer nel proprio costruttore. L'esempio seguente definisce una classe FileName il cui costruttore include un parametro StringComparer . L'oggetto StringComparer viene quindi usato nel metodo FileName.CompareTo .

using System;

public class FileName : IComparable
{
   string fname;
   StringComparer comparer;

   public FileName(string name, StringComparer comparer)
   {
      if (String.IsNullOrEmpty(name))
         throw new ArgumentNullException("name");

      this.fname = name;

      if (comparer != null)
         this.comparer = comparer;
      else
         this.comparer = StringComparer.OrdinalIgnoreCase;
   }

   public string Name
   {
      get { return fname; }
   }

   public int CompareTo(object obj)
   {
      if (obj == null) return 1;

      if (! (obj is FileName))
         return comparer.Compare(this.fname, obj.ToString());
      else
         return comparer.Compare(this.fname, ((FileName) obj).Name);
   }
}
Public Class FileName : Implements IComparable
    Dim fname As String
    Dim comparer As StringComparer

    Public Sub New(name As String, comparer As StringComparer)
        If String.IsNullOrEmpty(name) Then
            Throw New ArgumentNullException("name")
        End If

        Me.fname = name

        If comparer IsNot Nothing Then
            Me.comparer = comparer
        Else
            Me.comparer = StringComparer.OrdinalIgnoreCase
        End If
    End Sub

    Public ReadOnly Property Name As String
        Get
            Return fname
        End Get
    End Property

    Public Function CompareTo(obj As Object) As Integer _
           Implements IComparable.CompareTo
        If obj Is Nothing Then Return 1

        If Not TypeOf obj Is FileName Then
            obj = obj.ToString()
        Else
            obj = CType(obj, FileName).Name
        End If
        Return comparer.Compare(Me.fname, obj)
    End Function
End Class

String.Equals

Interpretazione predefinita: StringComparison.Ordinal.

La classe String consente di eseguire un test di uguaglianza chiamando gli overload del metodo Equals dell'istanza o statici oppure usando l'operatore di uguaglianza statico. Gli overload e l'operatore usano il confronto ordinale per impostazione predefinita. Tuttavia, si consiglia di chiamare comunque un overload che specifichi esplicitamente il tipo StringComparison anche se si vuole eseguire un confronto ordinale; questo semplifica la ricerca di codice per una determinata interpretazione della stringa.

String.ToUpper e String.ToLower

Interpretazione predefinita: StringComparison.CurrentCulture.

Prestare attenzione quando si usano i String.ToUpper() metodi e String.ToLower() , perché forzare una stringa a una stringa in maiuscolo o minuscolo viene spesso usata come una piccola normalizzazione per confrontare le stringhe indipendentemente dal caso. In questo caso, valutare l'uso di un confronto senza distinzione tra maiuscole e minuscole.

Sono disponibili anche i metodi String.ToUpperInvariant e String.ToLowerInvariant . ToUpperInvariant è la modalità standard di normalizzazione dei caratteri maiuscoli e minuscoli. A livello di comportamento, i confronti eseguiti con StringComparison.OrdinalIgnoreCase corrispondono alla composizione di due chiamate: la chiamata a ToUpperInvariant in entrambi gli argomenti di stringa e l'esecuzione di un confronto con StringComparison.Ordinal.

Sono disponibili anche overload per la conversione in maiuscole e minuscole in determinate impostazioni cultura, passando un oggetto CultureInfo che rappresenta le specifiche impostazioni cultura al metodo.

Char.ToUpper e Char.ToLower

Interpretazione predefinita: StringComparison.CurrentCulture.

I Char.ToUpper(Char) metodi e Char.ToLower(Char) funzionano in modo analogo ai String.ToUpper() metodi e String.ToLower() descritti nella sezione precedente.

String.StartsWith e String.EndsWith

Interpretazione predefinita: StringComparison.CurrentCulture.

Per impostazione predefinita, entrambi i metodi eseguono un confronto con distinzione delle impostazioni cultura.

String.IndexOf e String.LastIndexOf

Interpretazione predefinita: StringComparison.CurrentCulture.

La modalità di esecuzione dei confronti con gli overload predefiniti di questi metodi manca di coerenza. Tutti i metodi String.IndexOf e String.LastIndexOf che includono un parametro Char eseguono un confronto ordinale, ma i metodi predefiniti String.IndexOf e String.LastIndexOf che includono un parametro String eseguono un confronto con distinzione delle impostazioni cultura.

Se si chiama il metodo String.IndexOf(String) o String.LastIndexOf(String) e si passa una stringa da individuare nell'istanza corrente, si consiglia di chiamare un overload che specifichi esplicitamente il tipo StringComparison . Gli overload che includono un argomento Char non consentono di specificare un tipo StringComparison .

Metodi che eseguono indirettamente il confronto tra stringhe

Alcuni metodi non di tipo stringa in cui il confronto tra stringhe rappresenta l'operazione più importante usano il tipo StringComparer . La classe StringComparer include sei proprietà statiche che restituiscono istanze StringComparer i cui metodi StringComparer.Compare eseguono i tipi di confronto tra stringhe seguenti:

Array.Sort e Array.BinarySearch

Interpretazione predefinita: StringComparison.CurrentCulture.

Quando si archiviano dati in una raccolta o si leggono dati persistenti da un file o da un database nella raccolta, la modifica delle impostazioni cultura correnti può invalidare le invarianti nella raccolta. Il metodo Array.BinarySearch presuppone che gli elementi nella matrice da cercare siano già ordinati. Per ordinare qualsiasi elemento di tipo stringa nella matrice, il metodo Array.Sort chiama il metodo String.Compare per ordinare i singoli elementi. L'uso di un operatore di confronto con distinzione delle impostazioni cultura può essere pericoloso se le impostazioni cultura vengono modificate tra l'ordinamento della matrice e la ricerca dei contenuti. Nel codice seguente, ad esempio, le operazioni di archiviazione e recupero vengono eseguite sull'operatore di confronto fornito in modo implicito dalla proprietà Thread.CurrentThread.CurrentCulture . Se le impostazioni cultura possono cambiare tra le chiamate a StoreNames e DoesNameExiste, in particolare, se il contenuto della matrice viene conservato tra le due chiamate al metodo, è possibile che la ricerca binaria non riesca.

// Incorrect.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names); // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name) >= 0);  // Line B.
}
' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
    Dim index As Integer = 0
    ReDim storedNames(names.Length - 1)

    For Each name As String In names
        Me.storedNames(index) = name
        index += 1
    Next

    Array.Sort(names)          ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(Me.storedNames, name) >= 0      ' Line B.
End Function

L'esempio seguente mostra una variazione consigliata che usa lo stesso metodo di confronto ordinale (senza distinzione delle impostazioni cultura) sia per l'ordinamento che per la ricerca nella matrice. Il codice modificato si riflette nelle righe identificate con Line A e Line B nei due esempi.

// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.Ordinal);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0);  // Line B.
}
' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
    Dim index As Integer = 0
    ReDim storedNames(names.Length - 1)

    For Each name As String In names
        Me.storedNames(index) = name
        index += 1
    Next

    Array.Sort(names, StringComparer.Ordinal)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0      ' Line B.
End Function

Se i dati vengono conservati e spostati nelle impostazioni cultura e l'ordinamento viene usato per presentare i dati all'utente, potrebbe esser opportuno usare StringComparison.InvariantCulture, che garantisce un output utente migliore dal punto di vista linguistico ma non viene interessato dalle modifiche nelle impostazioni cultura. L'esempio seguente modifica i due esempi precedenti per usare la lingua inglese per l'ordinamento e la ricerca della matrice.

// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.InvariantCulture);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0);  // Line B.
}
' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
    Dim index As Integer = 0
    ReDim storedNames(names.Length - 1)

    For Each name As String In names
        Me.storedNames(index) = name
        index += 1
    Next

    Array.Sort(names, StringComparer.InvariantCulture)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0      ' Line B.
End Function

Esempio di raccolte: costruttore Hashtable

L'esecuzione dell'hashing nelle stringhe fornisce un secondo esempio di operazione interessata dal modo in cui vengono confrontate le stringhe.

L'esempio seguente crea un'istanza di un oggetto Hashtable passando l'oggetto StringComparer restituito dalla proprietà StringComparer.OrdinalIgnoreCase . Poiché una classe StringComparer derivata da StringComparer implementa l'interfaccia IEqualityComparer , il metodo GetHashCode viene usato per calcolare il codice hash delle stringhe nella tabella hash.

const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)
{
   h = new Hashtable(initialTableCapacity,
                     StringComparer.OrdinalIgnoreCase);

   foreach (string file in Directory.GetFiles(directory))
         h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)
{
   Object dt = h[targetFile];
   if (dt != null)
   {
      Console.WriteLine("File {0} was created at time {1}.",
         targetFile,
         (DateTime) dt);
   }
   else
   {
      Console.WriteLine("File {0} does not exist.", targetFile);
   }
}
Const initialTableCapacity As Integer = 100
Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)
    h = New Hashtable(initialTableCapacity, _
                      StringComparer.OrdinalIgnoreCase)

    For Each filename As String In Directory.GetFiles(dir)
        h.Add(filename, File.GetCreationTime(filename))
    Next
End Sub

Public Sub PrintCreationTime(targetFile As String)
    Dim dt As Object = h(targetFile)
    If dt IsNot Nothing Then
        Console.WriteLine("File {0} was created at {1}.", _
           targetFile, _
           CDate(dt))
    Else
        Console.WriteLine("File {0} does not exist.", targetFile)
    End If
End Sub

Vedi anche