Procedure consigliate per l'utilizzo di stringhe in .NET Framework
.NET Framework offre ampio supporto per lo sviluppo di applicazioni localizzate e globalizzate e semplifica l'applicazione delle convenzioni delle impostazioni cultura correnti o di impostazioni cultura specifiche quando si eseguono operazioni comuni come l'ordinamento e la visualizzazione di stringhe. L'ordinamento o il confronto di stringhe non è sempre un'operazione dipendente dalle impostazioni cultura. Le stringhe utilizzate internamente da un'applicazione, ad esempio, devono in genere essere gestite in modo identico con tutte le impostazioni cultura. Quando dati di tipo stringa indipendenti dalle impostazioni cultura, ad esempio tag XML, tag HTML, nomi utente, percorsi di file e nomi di oggetti di sistema, vengono interpretati come se fossero dipendenti dalle impostazioni cultura, il codice dell'applicazione può essere soggetto a bug di difficile individuazione, riduzione delle prestazioni e, in alcuni casi, problemi di sicurezza.
In questo argomento vengono analizzati i metodi di ordinamento, confronto e gestione di maiuscole e minuscole per le stringhe in .NET Framework versione 4 e versioni successive, vengono offerti suggerimenti per la selezione di un metodo di gestione delle stringhe appropriato e vengono fornite informazioni aggiuntive sui metodi di gestione delle stringhe.
Di seguito sono elencate le diverse sezioni di questo argomento:
Consigli sull'utilizzo delle stringhe
Specifica esplicita di confronti di stringhe
Dettagli sul confronto di stringhe
Scelta di un membro di StringComparison per la chiamata al metodo
Metodi di confronto di stringhe comuni in .NET Framework
Metodi per l'esecuzione del confronto di stringhe in modo indiretto
Consigli sull'utilizzo delle stringhe
Quando ci si occupa di sviluppo con .NET Framework, attenersi a questi semplici consigli relativi all'utilizzo di stringhe:
Utilizzare overload che specificano in modo esplicito le regole di confronto di stringhe per le operazioni sulle stringhe. In genere, a tale scopo è necessario chiamare un overload del metodo che disponga di un parametro di tipo StringComparison.
Utilizzare StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase per i confronti come impostazione predefinita sicura per l'individuazione di corrispondenze tra stringhe non dipendenti dalle impostazioni cultura.
Utilizzare i confronti con StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase per ottenere prestazioni migliori.
Utilizzare operazioni sulle stringhe basate su StringComparison.CurrentCulture quando si visualizza l'output all'utente.
Utilizzare i valori StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase non linguistici anziché operazioni sulle stringhe basate su CultureInfo.InvariantCulture quando il confronto non è rilevante dal punto di vista linguistico, ad esempio nel caso di simboli.
Utilizzare il metodo String.ToUpperInvariant anziché il metodo String.ToLowerInvariant quando si esegue la normalizzazione delle stringhe per il confronto.
Utilizzare un overload del metodo String.Equals per verificare se due stringhe sono uguali.
Utilizzare Compare e CompareTo per ordinare le stringhe, non per verificare l'uguaglianza.
Quando si utilizzano stringhe, evitare le operazioni seguenti:
Non utilizzare overload che non specificano in modo esplicito o implicito le regole di confronto di stringhe per le operazioni sulle stringhe.
Non utilizzare operazioni sulle stringhe basate su StringComparison.InvariantCulture nella maggior parte dei casi. Una delle poche eccezioni riguarda il caso in cui si salvano in modo permanente dati significativi dal punto di vista linguistico ma non dipendenti dalle impostazioni cultura.
Non utilizzare un overload del metodo String.Compare o CompareTo e verificare se viene restituito un valore pari a zero per determinare se due stringhe sono uguali.
Torna all'inizio
Specifica esplicita di confronti di stringhe
Per la maggior parte dei metodi di modifica delle stringhe in the.NET di Framework sono disponibili overload. In genere, uno o più overload accettano le impostazioni predefinite, mentre altri non accettano impostazioni predefinite e definiscono invece il modo preciso con cui le stringhe devono essere confrontate o modificate. La maggior parte dei metodi che non si basa sulle impostazioni predefinite include un parametro di tipo StringComparison, che rappresenta un'enumerazione che specifica in modo esplicito le regole per il confronto di stringhe in base a impostazioni cultura e utilizzo di maiuscole/minuscole. Nella tabella seguente sono descritti i membri dell'enumerazione StringComparison.
Membro di StringComparison |
Descrizione |
---|---|
Consente di eseguire un confronto in cui viene fatta distinzione tra maiuscole e minuscole utilizzando le impostazioni cultura correnti. |
|
Consente di eseguire un confronto in cui non viene fatta distinzione tra maiuscole e minuscole utilizzando le impostazioni cultura correnti. |
|
Consente di eseguire un confronto in cui viene fatta distinzione tra maiuscole e minuscole utilizzando la lingua inglese. |
|
Consente di eseguire un confronto in cui non viene fatta distinzione tra maiuscole e minuscole utilizzando la lingua inglese. |
|
Consente di eseguire un confronto ordinale. |
|
Consente di eseguire un confronto ordinale in cui non viene fatta distinzione tra maiuscole e minuscole. |
Il metodo IndexOf, che restituisce l'indice di una sottostringa in un oggetto String corrispondente a un carattere o a una stringa, dispone di nove overload:
IndexOf(Char), IndexOf(Char, Int32) e IndexOf(Char, Int32, Int32) che, per impostazione predefinita, consentono di eseguire una ricerca ordinale (con distinzione tra maiuscole e minuscole e indipendente dalle impostazioni cultura) per un carattere nella stringa.
IndexOf(String), IndexOf(String, Int32) e IndexOf(String, Int32, Int32) che, per impostazione predefinita, consentono di eseguire una ricerca con distinzione tra maiuscole e minuscole e dipendente dalle impostazioni cultura, per una sottostringa nella stringa.
IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison) e IndexOf(String, Int32, Int32, StringComparison) che includono un parametro di tipo StringComparison che consente di specificare il tipo di confronto.
È consigliabile selezionare un overload che non preveda l'utilizzo di valori predefiniti, per i motivi seguenti:
Alcuni overload con parametri predefiniti (che consentono di eseguire una ricerca di Char nell'istanza della stringa) eseguono un confronto ordinale, mentre altri (che consentono di eseguire una ricerca di una stringa nell'istanza della stringa) sono dipendenti dalle impostazioni cultura. È difficile da ricordare il valore predefinito utilizzato da ogni metodo e quindi è probabile che si faccia confusione con gli overload.
Lo scopo del codice che si basa su valori predefiniti per le chiamate ai metodi non è chiaro. Nell'esempio seguente, che si basa sulle impostazioni predefinite, è difficile stabilire se lo sviluppatore intendeva effettivamente eseguire un confronto ordinale o linguistico di due stringhe o se una differenza nell'utilizzo di maiuscole e minuscole tra protocol e "http" potrebbe comportare che il test di uguaglianza restituisca false.
Dim protocol As String = GetProtocol(url) If String.Equals(protocol, "http") Then ' ...Code to handle HTTP protocol. Else Throw New InvalidOperationException() End If
string protocol = GetProtocol(url); if (String.Equals(protocol, "http")) { // ...Code to handle HTTP protocol. } else { throw new InvalidOperationException(); }
In generale, è consigliabile chiamare un metodo che non si basi sulle impostazioni predefinite, poiché si evitano ambiguità nello scopo del codice. La lettura, la gestione e il debug del codice risultano inoltre più semplici. L'esempio seguente è equivalente al codice precedente, ad eccezione del fatto che risulta chiaro l'utilizzo del confronto ordinale e che le differenze tra maiuscole e minuscole vengono ignorate.
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
string protocol = GetProtocol(url);
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}
Torna all'inizio
Dettagli sul confronto di stringhe
Il confronto di stringhe è fondamentale per numerose operazioni correlate alle stringhe, in particolare per l'ordinamento e la verifica di uguaglianza. Le stringhe vengono ordinate in base a un ordine specifico: se "mia" appare prima di "stringa" in un elenco ordinato di stringhe, "mia" deve essere minore o uguale a "stringa". Il confronto definisce inoltre in modo implicito l'uguaglianza. L'operazione di confronto restituisce zero per le stringhe ritenute uguali. Questo comportamento può essere interpretato pensando che nessuna delle due stringhe viene ritenuta minore dell'altra. La maggior parte delle operazioni significative sulle stringhe include la procedura di confronto con un'altra stringa e quella di esecuzione di un'operazione di ordinamento ben definita o almeno una di queste due procedure.
La valutazione dell'ordinamento o dell'uguaglianza di due stringhe non restituisce tuttavia un singolo risultato corretto, ma il risultato dipende dai criteri utilizzati per confrontare le stringhe. In particolare, confronti di stringhe ordinali o basati sulle convenzioni di ordinamento o di utilizzo di maiuscole e minuscole delle impostazioni cultura correnti o della lingua inglese (impostazioni cultura non dipendenti dalle impostazioni locali ma basate sulla lingua inglese) potrebbero produrre risultati diversi.
Confronti di stringhe in cui vengono utilizzate le impostazioni cultura correnti
Un criterio prevede l'utilizzo delle convenzioni delle impostazioni cultura correnti nel confronto di stringhe. Per i confronti basati sulle impostazioni cultura correnti vengono utilizzate le impostazioni cultura o le impostazioni locali correnti del thread. Se le impostazioni cultura non sono state impostate dall'utente, equivalgono, per impostazione predefinita, all'impostazione nella finestra Opzioni internazionali nel Pannello di controllo. È consigliabile utilizzare sempre confronti basati sulle impostazioni cultura correnti quando i dati sono rilevanti dal punto di vista linguistico e quando riflettono l'interazione dell'utente in modo dipendente dalle impostazioni cultura.
Il comportamento di confronto e di gestione di maiuscole e minuscole in .NET Framework cambia tuttavia in base alle impostazioni cultura. Questo si verifica quando un'applicazione viene eseguita in un computer con impostazioni cultura diverse rispetto a quelle del computer con cui l'applicazione è stata sviluppata o quando vengono modificate le impostazioni cultura del thread in esecuzione. Questo comportamento è intenzionale, ma non risulta ovvio per molti sviluppatori. Nell'esempio seguente vengono illustrate le differenze per quanto riguarda l'ordinamento tra impostazioni cultura Inglese (Stati Uniti) ("en-US") e Svedese ("sv-SE"). Si noti che le parole "ångström", "Windows" e "Visual Studio" si trovano in posizioni diverse nelle matrici di stringhe ordinate.
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
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
I confronti senza distinzione tra maiuscole e minuscole in cui vengono utilizzate le impostazioni cultura correnti corrispondono ai confronti dipendenti dalle impostazioni cultura, ad eccezione del fatto che la distinzione tra maiuscole e minuscole viene ignorata, come richiesto dalle impostazioni cultura correnti del thread. È possibile che questo comportamento si manifesti anche negli ordinamenti.
I confronti in cui viene utilizzata la semantica delle impostazioni cultura correnti rappresentano l'impostazione predefinita per i metodi seguenti:
Overload di String.Compare che non includono un parametro StringComparison.
Overload di String.CompareTo.
Metodo String.StartsWith(String) predefinito e metodo String.StartsWith(String, Boolean, CultureInfo) con un parametro CultureInfo null.
Metodo String.EndsWith(String) predefinito e metodo String.EndsWith(String, Boolean, CultureInfo) con un parametro CultureInfo null.
Overload di String.IndexOf che accettano un oggetto String come parametro di ricerca e che non dispongono di un parametro StringComparison.
Overload di String.LastIndexOf che accettano un oggetto String come parametro di ricerca e che non dispongono di un parametro StringComparison.
In ogni caso, è consigliabile chiamare un overload che dispone di un parametro StringComparison per rendere chiaro lo scopo della chiamata al metodo.
Quando dati di tipo stringa non linguistici vengono interpretati in modo linguistico oppure quando dati di tipo stringa di impostazioni cultura specifiche vengono interpretati utilizzando le convenzioni di altre impostazioni cultura, possono verificarsi bug di più o meno difficile individuazione. L'esempio classico è quello relativo al problema della I turca.
Per quasi tutti gli alfabeti latini, incluso l'inglese (Stati Uniti), il carattere "i" (\u0069) è la versione minuscola del carattere "I" (\u0049). Questa regola relativa all'utilizzo di maiuscole e minuscole diventa rapidamente predefinita per coloro che si occupano di programmazione con tali impostazioni cultura. L'alfabeto turco ("tr-TR") include tuttavia un carattere "I con puntino, "İ", (\u0130) che è la versione maiuscola di "i". In questo alfabeto è presente anche una "i minuscola senza puntino", "ı" (\u0131), la cui versione maiuscola è "I". Questo comportamento si verifica anche con le impostazioni cultura Azeri ("az").
I presupposti relativi alla conversione di "i" in maiuscola o di "I" in minuscola non sono pertanto validi per tutte le impostazioni cultura. Se si utilizzano gli overload predefiniti per le routine di confronto di stringhe, questi saranno soggetti a variazioni in base alle impostazioni cultura. Se i dati da confrontare sono di tipo non linguistico, l'utilizzo degli overload predefiniti può produrre risultati indesiderati, come illustrato dal tentativo seguente di eseguire un confronto senza distinzione tra maiuscole e minuscole delle stringhe "file" e "FILE".
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
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
Questo confronto potrebbe provocare problemi significativi se le impostazioni cultura vengono utilizzate inavvertitamente nelle impostazioni relative alla sicurezza, come nell'esempio seguente. Una chiamata al metodo come IsFileURI("file:") restituisce true se le impostazioni cultura correnti sono quelle dell'inglese (Stati Uniti) o false se le impostazioni cultura correnti sono quelle del turco. Nei sistemi turchi, pertanto, qualcuno potrebbe aggirare le misure di sicurezza che bloccano l'accesso a URI in cui non viene fatta distinzione tra maiuscole e minuscole che iniziano con "FILE:".
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", True, Nothing)
End Function
public static bool IsFileURI(String path)
{
return path.StartsWith("FILE:", true, null);
}
In questo caso, poiché "file:" deve essere interpretato come un identificatore non linguistico indipendente dalle impostazioni cultura, il codice deve essere invece scritto come illustrato nell'esempio seguente.
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path)
{
path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
return true;
}
Nell'esempio precedente viene tuttavia utilizzato il metodo String.StartsWith(String, StringComparison) per verificare l'uguaglianza. Poiché lo scopo del confronto è quello di verificare l'uguaglianza anziché ordinare le stringhe, un'alternativa migliore consiste nel chiamare il metodo Equals, come illustrato nell'esempio seguente.
Public Shared Function IsFileURI(path As String) As Boolean
If (path.Length < 5) Then Return False
Return String.Equals(path.Substring(0, 5), "FILE:", _
StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path)
{
if (path.Length < 5) return false;
return String.Equals(path.Substring(0, 5), "FILE:",
StringComparison.OrdinalIgnoreCase);
}
Operazioni su stringhe ordinali
L'impostazione 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. Con i metodi richiamati con questi valori di StringComparison, le decisioni relative alle operazioni sulle stringhe vengono basate su semplici confronti di byte anziché su tabelle di equivalenza o relative all'utilizzo di maiuscole e minuscole i cui parametri sono definiti dalle impostazioni cultura. Nella maggior parte dei casi, questo approccio è più adeguato per garantire l'interpretazione desiderata delle stringhe e consente, nel contempo, di rendere il codice più veloce e semplice da leggere.
I confronti ordinali sono confronti di stringhe nei quali 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 C strcmp in fase di esecuzione. Utilizzare questo confronto quando il contesto richiede una corrispondenza esatta delle stringhe o criteri di corrispondenza conservativi. Il confronto ordinale rappresenta inoltre l'operazione di confronto più veloce perché non vengono applicate regole linguistiche per la determinazione di un risultato.
Le stringhe in .NET Framework possono contenere caratteri Null incorporati. Una delle differenze più evidenti tra confronto ordinale e confronto dipendente dalle impostazioni cultura (inclusi i confronti in cui viene utilizzata la lingua inglese) riguarda la gestione di caratteri Null incorporati in una stringa. Questi caratteri vengono ignorati quando si utilizzano i metodi String.Compare e String.Equals per eseguire confronti dipendenti dalle impostazioni cultura (inclusi i confronti in cui viene utilizzata la lingua inglese). Di conseguenza, nei confronti dipendenti dalle impostazioni cultura, le stringhe che contengono caratteri Null incorporati possono essere considerate uguali a quelle che non li contengono.
Importante |
---|
Sebbene i metodi di confronto di stringhe ignorino i caratteri Null incorporati, questo non avviene con i metodi di ricerca di stringhe, come String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf e String.StartsWith. |
Nell'esempio seguente viene eseguito un confronto dipendente dalle impostazioni cultura della stringa "Aa" con una stringa simile che contiene diversi caratteri Null incorporati tra "A" e "a" e viene illustrato come le due stringhe vengano considerate uguali.
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
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
Le stringhe non vengono tuttavia considerate uguali quando si utilizza 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 successivo approccio più conservativo. Questi confronti ignorano la distinzione tra maiuscole e minuscole nella maggior parte dei casi. Ad esempio, "windows" corrisponde a "Windows". In caso di utilizzo di caratteri ASCII, questi criteri sono equivalenti a StringComparison.Ordinal, ad eccezione del fatto che viene ignorato l'abituale utilizzo di maiuscole e minuscole ASCII. Qualsiasi carattere nell'intervallo [A, Z] (\u0041-\u005A) corrisponde pertanto al carattere corrispondente in [a, z] (\u0061-\007A). L'utilizzo di maiuscole e minuscole al di fuori dell'intervallo ASCII prevede l'utilizzo delle tabelle della lingua inglese. Il confronto seguente:
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
è pertanto equivalente (ma più rapido) a questo confronto:
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal)
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal);
Questi confronti sono molto rapidi.
Nota |
---|
Il comportamento delle stringhe di file system, chiavi e valori del Registro di sistema e variabili di ambiente è rappresentato in modo migliore da StringComparison.OrdinalIgnoreCase. |
Sia StringComparison.Ordinal che StringComparison.OrdinalIgnoreCase prevedono l'utilizzo diretto di valori binari e sono più appropriati per la corrispondenza. Quando non si è certi delle impostazioni di confronto, utilizzare uno di questi due valori. Poiché, tuttavia, viene eseguito un confronto byte per byte, l'ordinamento non viene eseguito in base a criteri linguistici (come in un dizionario della lingua inglese), bensì in base a criteri binari. I risultati possono apparire strani nella maggior parte dei contesti se vengono visualizzati agli utenti.
La semantica ordinale rappresenta l'impostazione predefinita per gli overload di String.Equals che non includono un argomento StringComparison (incluso l'operatore di uguaglianza). In ogni caso, è consigliabile chiamare un overload che dispone di un parametro StringComparison.
Operazioni sulle stringhe in cui viene utilizzata la lingua inglese
Per i confronti con la lingua inglese viene utilizzata la proprietà CompareInfo restituita dalla proprietà CultureInfo.InvariantCulture statica. Questo comportamento è lo stesso in tutti i sistemi e prevede la conversione dei caratteri non compresi nell'intervallo in quelli che vengono ritenuti caratteri equivalenti nella lingua inglese. Questi criteri possono essere utili per la gestione di un set di comportamenti delle stringhe in diverse impostazioni cultura, ma spesso forniscono risultati imprevisti.
I confronti senza distinzione tra maiuscole e minuscole con la lingua inglese prevedono l'utilizzo della proprietà CompareInfo statica restituita dalla proprietà CultureInfo.InvariantCulture anche per le informazioni sul confronto. Qualsiasi differenzia tra maiuscole e minuscole in questi caratteri convertiti viene ignorata.
I confronti che utilizzano StringComparison.InvariantCulture e StringComparison.Ordinal funzionano in modo identico con le stringhe ASCII. Tramite StringComparison.InvariantCulture vengono tuttavia prese decisioni linguistiche che potrebbero non essere appropriate per le stringhe che devono essere interpretate come set di byte. L'oggetto CultureInfo.InvariantCulture.CompareInfo fa in modo che il metodo Compare interpreti determinati set di caratteri come equivalenti. L'equivalenza seguente è ad esempio valida con la lingua inglese:
InvariantCulture: a + ̊ = å
Il carattere LATIN SMALL LETTER A "a" (\u0061) quando è accanto al carattere COMBINING RING ABOVE "+ " ̊" (\u030a), viene interpretato come carattere LATIN SMALL LETTER A WITH RING ABOVE "å" (\u00e5). Come illustrato nell'esempio seguente, questo comportamento è diverso rispetto al confronto ordinale.
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
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
Quando vengono interpretati nomi file, cookie o qualsiasi altro elemento in cui può essere presente una combinazione di "å", i confronti ordinali offrono ancora il comportamento più trasparente e appropriato.
In generale, la lingua inglese dispone di pochissime proprietà che la rendono utile per il confronto. Il confronto viene eseguito in modo rilevante dal punto di vista linguistico, senza garantire un'equivalenza completa dal punto di vista simbolico, ma non rappresenta la scelta appropriata per la visualizzazione con qualsiasi tipo di impostazioni cultura. Una delle poche ragioni per utilizzare StringComparison.InvariantCulture per il confronto consiste nell'esigenza di salvare in modo permanente dati ordinati per una visualizzazione identica con diverse impostazioni cultura. Se, ad esempio, a un'applicazione è associato un file di dati di grandi dimensioni che contiene un elenco di identificatori ordinati per la visualizzazione, l'aggiunta a tale elenco richiederebbe un inserimento con ordinamento di tipo invariante.
Torna all'inizio
Scelta di un membro di StringComparison per la chiamata al metodo
Nella tabella seguente è illustrato il mapping tra il contesto di stringa semantico e un membro dell'enumerazione StringComparison.
Dati |
Comportamento |
Valore di System.StringComparison corrispondente |
---|---|---|
Identificatori interni con distinzione tra maiuscole e minuscole. Identificatori con distinzione tra maiuscole e minuscole in formati standard come XML e HTTP. Impostazioni correlate alla sicurezza con distinzione tra maiuscole e minuscole. |
Identificatore non linguistico, in cui i byte corrispondono esattamente. |
|
Identificatori interni senza distinzione tra maiuscole e minuscole. Identificatori senza distinzione tra maiuscole e minuscole in formati standard come XML e HTTP. Percorsi di file. Valori e chiavi del Registro di sistema. Variabili di ambiente. Identificatori delle risorse (ad esempio nomi di handle). Impostazioni correlate 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 di Windows. |
|
Alcuni dati persistenti, rilevanti dal punto di vista linguistico. Visualizzazione di dati linguistici che richiedono un ordinamento fisso. |
Dati non dipendenti dalle impostazioni cultura ma rilevanti dal punto di vista linguistico. |
-oppure- |
Dati visualizzati all'utente. La maggior parte dell'input utente. |
Dati che richiedono un utilizzo linguistico locale. |
-oppure- |
Torna all'inizio
Metodi di confronto di stringhe comuni in .NET Framework
Nelle sezioni seguenti vengono descritti i metodi maggiormente utilizzati per il confronto di stringhe.
String.Compare
Interpretazione predefinita: StringComparison.CurrentCulture.
Come operazione fondamentale per l'interpretazione delle stringhe, tutte le istanze di queste chiamate al metodo devono essere esaminate per determinare se le stringhe devono essere interpretate sulla base delle impostazioni cultura correnti oppure essere dissociate dalle impostazioni cultura (dal punto di vista simbolico). 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, senza considerazione degli spazi vuoti, senza considerazione del tipo kana e così via) per mezzo dell'enumerazione del flag CompareOptions.
String.CompareTo
Interpretazione predefinita: StringComparison.CurrentCulture.
Questo metodo non offre attualmente un overload che specifica un tipo StringComparison. In genere è possibile convertire questi metodi nel formato String.Compare(String, String, StringComparison) consigliato.
I tipi che implementano le interfacce IComparable e IComparable<T> implementano questo metodo. Poiché non viene offerta l'opzione di un parametro StringComparison, i tipi che implementano il metodo consentono spesso all'utente di specificare un oggetto StringComparer nel costruttore. Nell'esempio seguente viene definita una classe FileName il cui costruttore di classe include un parametro StringComparer. Questo oggetto StringComparer viene quindi utilizzato nel metodo FileName.CompareTo.
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
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);
}
}
String.Equals
Interpretazione predefinita: StringComparison.Ordinal.
La classe String consente di verificare l'uguaglianza chiamando gli overload del metodo Equals statico o di istanza o utilizzando l'operatore di uguaglianza statico. Gli overload e l'operatore utilizzano il confronto ordinale per impostazione predefinita. È tuttavia consigliabile chiamare un overload che specifica in modo esplicito il tipo StringComparison anche se si desidera eseguire un confronto ordinale. In questo modo, è possibile semplificare la ricerca di una determinata interpretazione di stringa nel codice.
String.ToUpper e String.ToLower
Interpretazione predefinita: StringComparison.CurrentCulture.
È necessario prestare attenzione quando si utilizzano questi metodi in quanto il metodo di forzare una stringa in modo che appaia maiuscola o minuscola viene spesso utilizzato come normalizzazione minore per il confronto di stringhe indipendentemente dall'utilizzo di maiuscole o minuscole. In tal caso, prendere in considerazione l'utilizzo di un confronto senza distinzione tra maiuscole e minuscole.
Sono inoltre disponibili i metodi String.ToUpperInvariant e String.ToLowerInvariant. Il metodo ToUpperInvariant rappresenta la modalità standard per la normalizzazione di maiuscole e minuscole. I confronti eseguiti utilizzando StringComparison.OrdinalIgnoreCase rappresentano, per quanto riguarda il comportamento, l'unione di due chiamate: la chiamata a ToUpperInvariant su entrambi gli argomenti di stringa e l'esecuzione di un confronto utilizzando StringComparison.Ordinal.
Sono inoltre disponibili overload per la conversione in maiuscolo e minuscolo in impostazioni cultura specifiche, passando un oggetto CultureInfo che rappresenta le impostazioni cultura al metodo.
Char.ToUpper e Char.ToLower
Interpretazione predefinita: StringComparison.CurrentCulture.
Questi metodi funzionano in modo analogo ai metodi String.ToUpper e String.ToLower descritti nella sezione precedente.
String.StartsWith e String.EndsWith
Interpretazione predefinita: StringComparison.CurrentCulture.
Per impostazione predefinita, entrambi questi metodi eseguono un confronto dipendente dalle impostazioni cultura.
String.IndexOf e String.LastIndexOf
Interpretazione predefinita: StringComparison.CurrentCulture.
Non vi è coerenza nel modo in cui vengono eseguiti i confronti dagli overload predefiniti di questi metodi. Tutti i metodi String.IndexOf e String.LastIndexOf che includono un parametro Char eseguono un confronto ordinale, ma i metodi String.IndexOf e String.LastIndexOf predefiniti che includono un parametro String eseguono un confronto dipendente dalle impostazioni cultura.
Se si chiama il metodo String.IndexOf(String) o String.LastIndexOf(String) e si passa una stringa da individuare nell'istanza corrente, è consigliabile chiamare un overload che specifichi in modo esplicito il tipo StringComparison. Gli overload che includono un argomento Char non consentono di specificare un tipo StringComparison.
Torna all'inizio
Metodi per l'esecuzione del confronto di stringhe in modo indiretto
Alcuni metodi non di tipo stringa che prevedono il confronto di stringhe come operazione centrale utilizzano il tipo StringComparer. La classe StringComparer include sei proprietà statiche che restituiscono istanze di StringComparer i cui metodi StringComparer.Compare eseguono i tipi seguenti di confronti di stringhe:
Confronti di stringhe dipendenti dalle impostazioni cultura eseguiti utilizzando le impostazioni cultura correnti. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.CurrentCulture.
Confronti in cui non viene fatta distinzione tra maiuscole e minuscole eseguiti utilizzando le impostazioni cultura correnti. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.CurrentCultureIgnoreCase.
Confronti indipendenti dalle impostazioni cultura eseguiti utilizzando le regole di confronto delle parole della lingua inglese. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.InvariantCulture.
Confronti indipendenti dalle impostazioni cultura e in cui non viene fatta distinzione tra maiuscole e minuscole eseguiti utilizzando le regole di confronto delle parole della lingua inglese. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.InvariantCultureIgnoreCase.
Confronto ordinale. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.Ordinal.
Confronto ordinale in cui non viene fatta distinzione tra maiuscole e minuscole. Questo oggetto StringComparer viene restituito dalla proprietà StringComparer.OrdinalIgnoreCase.
Array.Sort e Array.BinarySearch
Interpretazione predefinita: StringComparison.CurrentCulture.
Quando si archiviano dati di qualsiasi tipo in un insieme, o si leggono dati salvati in modo permanente da un file o un database in un insieme, la modifica delle impostazioni cultura correnti può invalidare le invarianti nell'insieme. Il metodo Array.BinarySearch presuppone che gli elementi nella matrice in cui viene eseguita la ricerca siano già ordinati. Per ordinare qualsiasi elemento di tipo stringa nella matrice, il metodo Array.Sort chiama il metodo String.Compare che consente di ordinare i singoli elementi. L'utilizzo di un operatore di confronto dipendente dalle impostazioni cultura può essere rischioso se le impostazioni cultura vengono modificate tra il momento in cui la matrice viene ordinata e quello in cui viene eseguita la ricerca nel relativo contenuto. Nel codice seguente, ad esempio, le operazioni di archiviazione e recupero vengono eseguite nell'operatore di confronto fornito in modo implicito dalla proprietà Thread.CurrentThread.CurrentCulture. Se le impostazioni cultura possono cambiare tra le chiamate a StoreNames e DoesNameExist e, in particolare, se il contenuto della matrice viene salvato in modo permanente tra le due chiamate al metodo, è possibile che la ricerca binaria non riesca.
' 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
// 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.
}
Nell'esempio seguente viene illustrata una variazione consigliata, che prevede l'utilizzo dello stesso metodo di confronto ordinale (indipendente dalle impostazioni cultura) sia per ordinare che per eseguire ricerche nella matrice. Il codice di modifica si riflette nelle righe identificate come Line A e Line B nei due esempi.
' 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
// 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.
}
Se questi dati vengono salvati in modo permanente e spostati tra diverse impostazioni cultura e l'ordinamento viene utilizzato per presentare questi dati all'utente, prendere in considerazione l'utilizzo di StringComparison.InvariantCulture che garantisce un output utente migliore dal punto di vista linguistico ma non viene influenzato dai cambiamenti nelle impostazioni cultura. Nell'esempio seguente vengono modificati i due esempi precedenti in modo da utilizzare la lingua inglese per l'ordinamento e la ricerca nella matrice.
' 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
// 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.
}
Esempio di insiemi: costruttore Hashtable
La generazione di hash di stringhe fornisce un secondo esempio di operazione influenzata dal modo in cui vengono confrontate le stringhe.
Nell'esempio seguente viene creata un'istanza di un oggetto Hashtable passando l'oggetto StringComparer restituito dalla proprietà StringComparer.OrdinalIgnoreCase. Poiché un oggetto StringComparer di classe derivato da StringComparer implementa l'interfaccia IEqualityComparer, il relativo metodo GetHashCode viene utilizzato per calcolare il codice hash delle stringhe nella tabella hash.
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
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);
}
}
Torna all'inizio