Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
.NET offre un ampio supporto per lo sviluppo di applicazioni localizzate e globalizzate e semplifica l'applicazione delle convenzioni culturali della cultura corrente o di una cultura specifica quando si eseguono operazioni comuni, come l'ordinamento e la visualizzazione di stringhe. L'ordinamento o il confronto delle stringhe non è sempre un'operazione sensibile alle differenze culturali. Ad esempio, le stringhe utilizzate all'interno di un'applicazione devono essere gestite in modo identico in tutte le culture. Quando i dati stringa indipendenti dalla lingua, come tag XML, tag HTML, nomi utente, percorsi di file e nomi di oggetti di sistema, vengono interpretati come se fossero sensibili alle impostazioni culturali, il codice dell'applicazione può essere soggetto a bug sottili, prestazioni scarse e, in alcuni casi, problemi di sicurezza.
Questo articolo esamina i metodi di ordinamento, confronto e gestione delle maiuscole e minuscole di stringhe in .NET, presenta consigli per la selezione di un metodo di gestione delle stringhe appropriato e fornisce informazioni aggiuntive sui metodi di gestione delle stringhe.
Raccomandazioni per l'utilizzo delle stringhe
Quando si sviluppa con .NET, seguire queste indicazioni quando si confrontano le stringhe.
Suggerimento
Diversi metodi correlati a stringhe eseguono il confronto. Gli esempi includono String.Equals, String.Compare, String.IndexOfe String.StartsWith.
- Usare overload che specificano in modo esplicito le regole di confronto delle stringhe per le operazioni di stringa. In genere, ciò comporta la chiamata di un overload del metodo con un parametro di tipo StringComparison.
- Utilizzare StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase per i confronti come impostazione predefinita per garantire una corrispondenza di stringhe indipendente dalle impostazioni cultura.
- Usare confronti con StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase per ottenere prestazioni migliori.
- Utilizzare operazioni sulle stringhe basate su StringComparison.CurrentCulture quando si visualizza l'output per l'utente.
- Usare i valori non linguistici StringComparison.Ordinal o StringComparison.OrdinalIgnoreCase anziché le operazioni stringa basate su CultureInfo.InvariantCulture quando il confronto è linguisticamente irrilevante (ad esempio simbolico).
- Usare il metodo String.ToUpperInvariant anziché il metodo String.ToLowerInvariant quando si normalizzano le stringhe per il confronto.
- Utilizzare l'overload del metodo String.Equals per verificare se due stringhe sono uguali.
- Utilizzare i String.Compare metodi e String.CompareTo per ordinare le stringhe, non per verificare l'uguaglianza.
- Usare la formattazione sensibile alle impostazioni cultura per visualizzare dati non stringa, ad esempio numeri e date, in un'interfaccia utente. Utilizzare la formattazione con la cultura invariante per memorizzare i dati non stringa in forma di stringa.
Evita le pratiche seguenti quando confronti stringhe:
- Non usare overload che non specificano esplicitamente o implicitamente le regole di confronto delle stringhe nelle operazioni sulle stringhe.
- Non usare operazioni di stringa basate sulla StringComparison.InvariantCulture maggior parte dei casi. Una delle poche eccezioni consiste nel rendere persistenti i dati linguistici significativi ma indipendenti dalla cultura.
- Non usare un overload del String.Compare metodo o CompareTo e verificare se il valore restituito è zero per determinare se due stringhe sono uguali.
Specificare esplicitamente i confronti tra stringhe
La maggior parte dei metodi di manipolazione delle stringhe in .NET è in overload. In genere, uno o più overload accettano impostazioni predefinite, mentre altre non accettano impostazioni predefinite e definiscono invece il modo preciso in cui le stringhe devono essere confrontate o modificate. La maggior parte dei metodi che non si basano sulle impostazioni predefinite include un parametro di tipo StringComparison, che è un'enumerazione che specifica in modo esplicito le regole per il confronto tra stringhe in base alle impostazioni cultura e maiuscole/minuscole. Nella tabella seguente vengono descritti i membri dell'enumerazione StringComparison .
Membro della classe 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 utilizzando la cultura invariante. |
InvariantCultureIgnoreCase | Esegue un confronto senza distinzione tra maiuscole e minuscole utilizzando la cultura invariante. |
Ordinal | Esegue un confronto ordinale. |
OrdinalIgnoreCase | Esegue un confronto ordinale che non fa distinzione tra maiuscole e minuscole. |
Ad esempio, il IndexOf metodo , che restituisce l'indice di una sottostringa in un String oggetto che corrisponde a un carattere o a una stringa, ha nove overload:
- IndexOf(Char), IndexOf(Char, Int32)e IndexOf(Char, Int32, Int32), che per impostazione predefinita esegue una ricerca ordinale (senza distinzione tra maiuscole e minuscole e impostazioni cultura) per un carattere nella stringa.
- IndexOf(String), IndexOf(String, Int32), e IndexOf(String, Int32, Int32), che per impostazione predefinita esegue una ricerca sensibile al maiuscolo e minuscolo e sensibile alla 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 formato del confronto.
È consigliabile selezionare un overload che non usa valori predefiniti, per i motivi seguenti:
Alcuni sovraccarichi con parametri di default (quelli che cercano un oggetto Char nell'istanza di stringa) eseguono un confronto ordinale, mentre altri (quelli che cercano una stringa nell'istanza della stringa) sono sensibili alla cultura. È difficile ricordare quale metodo usa quale valore predefinito e facile confondere gli overload.
La finalità del codice che si basa sui valori predefiniti per le chiamate al metodo non è chiara. Nell'esempio seguente, che si basa sulle impostazioni predefinite, è difficile sapere se lo sviluppatore ha effettivamente inteso un confronto ordinale o un confronto linguistico di due stringhe, o se una differenza di maiuscole e minuscole tra
url.Scheme
e "https" potrebbe causare il test per l'uguaglianza a restituirefalse
.Uri url = new("https://learn.microsoft.com/"); // Incorrect if (string.Equals(url.Scheme, "https")) { // ...Code to handle HTTPS protocol. }
Dim url As New Uri("https://learn.microsoft.com/") ' Incorrect If String.Equals(url.Scheme, "https") Then ' ...Code to handle HTTPS protocol. End If
In generale, è consigliabile chiamare un metodo che non si basa sulle impostazioni predefinite, perché rende la finalità del codice non ambigua. Questo, a sua volta, rende il codice più leggibile e più facile da eseguire per il debug e la gestione. Nell'esempio seguente vengono affrontate le domande generate sull'esempio precedente. Rende chiaro che viene usato il confronto ordinale e che le differenze nel caso vengano ignorate.
Uri url = new("https://learn.microsoft.com/");
// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
// ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")
' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTPS protocol.
End If
Dettagli del confronto tra stringhe
Il confronto tra stringhe è il cuore di molte operazioni relative alle stringhe, in particolare l'ordinamento e la verifica dell'uguaglianza. Le stringhe vengono ordinate in un ordine determinato: se "my" viene visualizzato prima di "string" in un elenco ordinato di stringhe, "my" deve essere inferiore o uguale a "string". Inoltre, il confronto definisce in modo implicito l'uguaglianza. L'operazione di confronto restituisce zero per le stringhe ritenute uguali. Una buona interpretazione è che nessuna delle due stringhe è minore dell'altra. Le operazioni più significative che coinvolgono stringhe includono una o entrambe queste procedure: confronto con un'altra stringa ed esecuzione di un'operazione di ordinamento ben definita.
Annotazioni
È possibile scaricare le Tabelle dei Pesi di Ordinamento, un insieme di file di testo che contengono informazioni sui pesi dei caratteri utilizzati nelle operazioni di ordinamento e confronto per i sistemi operativi Windows, e la Tabella degli Elementi di Collazione Unicode Predefiniti, la versione più recente della tabella dei pesi di ordinamento per Linux e macOS. La versione specifica della tabella dei pesi di ordinamento in Linux e macOS dipende dalla versione delle librerie International Components for Unicode installate nel sistema. Per informazioni sulle versioni di ICU e sulle versioni Unicode implementate, vedere Download di ICU.
Tuttavia, la valutazione di due stringhe per l'uguaglianza o l'ordinamento non restituisce un singolo risultato corretto; il risultato dipende dai criteri usati per confrontare le stringhe. In particolare, i confronti di stringhe che sono ordinali o basati sulle convenzioni di maiuscole e minuscole della cultura corrente o della cultura invariante (una cultura indipendente dalle impostazioni locali basata sulla lingua inglese) possono produrre risultati diversi.
Inoltre, i confronti tra stringhe che usano versioni diverse di .NET o l'uso di .NET in sistemi operativi diversi o versioni del sistema operativo possono 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 durante il confronto delle stringhe. I confronti basati sulla cultura corrente utilizzano la cultura o il locale attuale del thread di esecuzione. Se la cultura non è definita dall'utente, per impostazione predefinita viene utilizzata quella del sistema operativo. Dovresti sempre usare confronti basati sulle impostazioni culturali correnti quando i dati sono linguisticamente pertinenti e quando riflettono interazioni con l'utente sensibili alle differenze culturali.
Tuttavia, il comportamento di confronto e di maiuscole e minuscole in .NET cambia quando cambiano le impostazioni culturali. Ciò si verifica quando un'applicazione viene eseguita in un computer con impostazioni cultura diverse rispetto al computer in cui è stata sviluppata l'applicazione o quando il thread in esecuzione modifica le impostazioni cultura. Questo comportamento è intenzionale, ma rimane non ovvio per molti sviluppatori. L'esempio seguente illustra le differenze nell'ordinamento delle culture inglese statunitense ("en-US") e 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.Globalization;
// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
// Current culture
Array.Sort(values);
DisplayArray(values);
// Change culture to Swedish (Sweden)
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);
// Restore the original culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
static void DisplayArray(string[] values)
{
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
foreach (string value in values)
Console.WriteLine($" {value}");
Console.WriteLine();
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// apple
// Visual Studio
// Windows
// ångström
// Æble
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
"Windows", "Visual Studio"}
' Current culture
Array.Sort(values)
DisplayArray(values)
' Change culture to Swedish (Sweden)
Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)
' Restore the original culture
Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub
Sub DisplayArray(values As String())
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")
For Each value As String In values
Console.WriteLine($" {value}")
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' apple
' Visual Studio
' Windows
' ångström
' Æble
I confronti senza distinzione tra maiuscole e minuscole che usano la cultura corrente sono uguali ai confronti sensibili alla cultura, tranne per il fatto che ignorano la distinzione tra maiuscole e minuscole come stabilito dalla cultura corrente del thread. Questo comportamento può manifestarsi anche in ordini di classificazione.
I confronti che usano la semantica delle impostazioni cultura correnti sono l'impostazione predefinita per i metodi seguenti:
- String.Compare overload che non includono un StringComparison parametro.
- String.CompareTo Overload.
- Il metodo predefinito String.StartsWith(String) e il String.StartsWith(String, Boolean, CultureInfo) metodo con un
null
CultureInfo parametro . - Il metodo predefinito String.EndsWith(String) e il String.EndsWith(String, Boolean, CultureInfo) metodo con un
null
CultureInfo parametro . - String.IndexOf overload che accettano un oggetto String come parametro di ricerca e che non hanno un StringComparison parametro.
- String.LastIndexOf overload che accettano un oggetto String come parametro di ricerca e che non hanno un StringComparison parametro.
In ogni caso, è consigliabile chiamare un overload con un StringComparison parametro per rendere chiara la finalità della chiamata al metodo.
I bug sottili e non così sottili possono emergere quando i dati delle stringhe non linguistici sono interpretati in modo linguistico o quando i dati stringa di una determinata cultura sono interpretati usando le convenzioni di un'altra cultura. L'esempio canonico è il problema Turkish-I.
Per quasi tutti gli alfabeti latini, inclusi l'inglese degli Stati Uniti, il carattere "i" (\u0069) è la versione minuscola del carattere "I" (\u0049). Questa regola di maiuscole e minuscole diventa rapidamente l'impostazione predefinita per un utente che programma in tali impostazioni cultura. Tuttavia, l'alfabeto turco ("tr-TR") include un carattere "I con un punto" "İ" (\u0130), che è la versione maiuscola di "i". Turco include anche un carattere minuscolo "i senza un punto", "ı" (\u0131), che maiuscole in "I". Questo comportamento si verifica anche nelle impostazioni cultura azere ("az").
Pertanto, i presupposti fatti riguardo alla maiuscola "I" o alla minuscola "i" non sono validi in tutte le culture. Se si utilizzano gli overload predefiniti per le routine di confronto tra stringhe, saranno soggetti a variazioni tra culture. Se i dati da confrontare non sono linguistici, l'uso degli overload predefiniti può produrre risultati indesiderati, come illustrato nel tentativo seguente di eseguire un confronto senza distinzione tra maiuscole e minuscole delle stringhe "bill" e "BILL".
using System.Globalization;
string name = "Bill";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
//' The example displays the following output:
//'
//' Culture = English (United States)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? True
//'
//' Culture = Turkish (Türkiye)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
Dim name As String = "Bill"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
End Sub
End Module
' The example displays the following output:
'
' Culture = English (United States)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? True
'
' Culture = Turkish (Türkiye)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? False
Questo confronto può causare problemi significativi se le impostazioni cultura vengono usate inavvertitamente nelle impostazioni sensibili alla sicurezza, come nell'esempio seguente. Una chiamata al metodo come IsFileURI("file:")
restituisce true
se le impostazioni culturali correnti sono inglese statunitense, ma false
se le impostazioni culturali correnti sono turche. Pertanto, 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) =>
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 un identificatore non linguistico insensibile alla cultura, il codice dovrebbe invece essere scritto come nell'esempio seguente:
public static bool IsFileURI(string path) =>
path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
Operazioni di stringa ordinale
Se si specifica il StringComparison.Ordinal valore o StringComparison.OrdinalIgnoreCase in una chiamata al metodo, si intende un confronto non linguistico in cui le caratteristiche dei linguaggi naturali vengono ignorate. I metodi richiamati con questi StringComparison valori fondano le decisioni relative alle operazioni sulle stringhe su semplici confronti di byte, anziché su tabelle di maiuscole/minuscole o di equivalenza parametrizzati da fattori culturali. Nella maggior parte dei casi, questo approccio si adatta meglio all'interpretazione desiderata delle stringhe, rendendo il codice più veloce e affidabile.
I confronti ordinali sono confronti di stringhe in cui ogni byte di ogni stringa viene confrontata senza interpretazione linguistica; Ad esempio, "windows" non corrisponde a "Windows". Si tratta essenzialmente di una chiamata alla funzione di runtime strcmp
C. Usare questo confronto quando il contesto determina che le stringhe devono corrispondere esattamente o richiedono criteri di corrispondenza conservativi. Inoltre, il confronto ordinale è l'operazione di confronto più veloce perché non applica regole linguistiche quando si determina un risultato.
Le stringhe in .NET possono contenere caratteri Null incorporati (e altri caratteri non stampati). Una delle differenze più chiare tra confronto ordinale e sensibile alla cultura (inclusi i confronti che usano la cultura invariante) 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 sensibili alle impostazioni cultura (inclusi i confronti che usano le impostazioni cultura invarianti). Di conseguenza, le stringhe che contengono caratteri Null incorporati possono essere considerate uguali alle stringhe che non lo fanno. I caratteri non stampati incorporati potrebbero essere ignorati allo scopo di metodi di confronto di stringhe, ad esempio String.StartsWith.
Importante
Anche se i metodi di confronto tra stringhe ignorano i caratteri Null incorporati, i metodi di ricerca di stringhe, ad String.Containsesempio , String.EndsWithString.IndexOf, String.LastIndexOf, e String.StartsWith non .
Nell'esempio seguente viene eseguito un confronto sensibile alle impostazioni cultura della stringa "Aa" con una stringa simile che contiene diversi caratteri Null incorporati tra "A" e "a" e mostra come le due stringhe vengono considerate uguali:
string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";
Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");
string ShowBytes(string value)
{
string hexString = string.Empty;
for (int index = 0; index < value.Length; index++)
{
string result = Convert.ToInt32(value[index]).ToString("X4");
result = string.Concat(" ", result.Substring(0,2), " ", result.Substring(2, 2));
hexString += result;
}
return hexString.Trim();
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
End Sub
Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Current Culture: 0
' Invariant Culture: 0
' With String.Equals:
' Current Culture: True
' Invariant Culture: True
End Module
Tuttavia, le stringhe non vengono considerate uguali quando si usa il confronto ordinale, come illustrato nell'esempio seguente:
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");
string ShowBytes(string str)
{
string hexString = string.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
End Sub
Function ShowBytes(str As String) As String
Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False
End Module
I confronti ordinali senza distinzione tra maiuscole e minuscole rappresentano l'approccio più conservativo successivo. Questi confronti ignorano la maggior parte delle maiuscole e minuscole; Ad esempio, "windows" corrisponde a "Windows". Quando si gestiscono caratteri ASCII, questo criterio è equivalente a StringComparison.Ordinal, ad eccezione del fatto che ignora la consueta distinzione tra maiuscole e minuscole ASCII. Pertanto, qualsiasi carattere in [A, Z] (\u0041-\u005A) corrisponde al carattere corrispondente in [a,z] (\u0061-\007A). La conversione delle maiuscole e minuscole al di fuori dell'intervallo ASCII utilizza le tabelle della cultura invariante. Di conseguenza, il confronto seguente:
string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
equivale a (ma più veloce di) questo confronto:
string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)
Questi confronti sono ancora molto veloci.
Sia StringComparison.Ordinal che StringComparison.OrdinalIgnoreCase usano direttamente i valori binari e sono più adatti per la corrispondenza. Quando non si è certi delle impostazioni di confronto, usare uno di questi due valori. Tuttavia, poiché eseguono un confronto di byte per byte, non ordinano in base a un ordinamento linguistico (ad esempio un dizionario inglese) ma in base a un ordinamento binario. I risultati potrebbero risultare strani nella maggior parte dei contesti se visualizzati agli utenti.
La semantica ordinale è l'impostazione predefinita per gli overload String.Equals che non includono StringComparison argomento (incluso l'operatore di uguaglianza). In ogni caso, è consigliabile chiamare un overload che ha come parametro StringComparison.
Operazioni sulle stringhe che usano la cultura invariante
I confronti con la cultura invariante usano la proprietà CompareInfo restituita dalla proprietà statica CultureInfo.InvariantCulture. Questo comportamento è lo stesso in tutti i sistemi; converte tutti i caratteri al di fuori del suo intervallo in ciò che ritiene siano caratteri invarianti equivalenti. Questo criterio può essere utile per mantenere un set di comportamenti di stringa tra le impostazioni cultura, ma spesso fornisce risultati imprevisti.
I confronti senza distinzione tra maiuscole e minuscole con la cultura invariante usano anche la proprietà statica CompareInfo restituita dalla proprietà statica CultureInfo.InvariantCulture per le informazioni di confronto. Le differenze tra maiuscole e minuscole tra questi caratteri tradotti vengono ignorate.
Confronti che usano StringComparison.InvariantCulture e StringComparison.Ordinal funzionano allo stesso modo sulle stringhe ASCII. Tuttavia, StringComparison.InvariantCulture prende decisioni linguistiche che potrebbero non essere appropriate per le stringhe che devono essere interpretate come un set di byte. L'oggetto CultureInfo.InvariantCulture.CompareInfo
rende il Compare metodo interpretare determinati set di caratteri come equivalenti. Ad esempio, la seguente equivalenza è valida nella cultura invariante:
InvariantCulture: a + ̊ = å
Il carattere LETTERA MINUSCOLA LATINA A "a" (\u0061), quando si trova accanto al carattere ANELLO COMBINATORE SOPRA "+ ̊" (\u030a), viene interpretato come il carattere LETTERA MINUSCOLA LATINA A CON ANELLO SOPRA "å" (\u00e5). Come illustrato nell'esempio seguente, questo comportamento è diverso dal confronto ordinale.
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine($"Equal sort weight of {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");
Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {string.Compare(separated, combined, StringComparison.Ordinal) == 0}");
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False
Module Program
Sub Main()
Dim separated As String = ChrW(&H61) & ChrW(&H30A)
Dim combined As String = ChrW(&HE5)
Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
separated, combined,
String.Compare(separated, combined, StringComparison.InvariantCulture) = 0)
Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
separated, combined,
String.Compare(separated, combined, StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False
End Sub
End Module
Quando si interpretano nomi di file, cookie o qualsiasi altra opzione in cui può essere visualizzata una combinazione come "å", i confronti ordinali offrono comunque il comportamento più trasparente e appropriato.
Nel complesso, la cultura invariante ha poche proprietà che la rendono utile per il confronto. Esegue un confronto in modo linguistico rilevante, che impedisce di garantire l'equivalenza simbolica completa, ma non è la scelta per la visualizzazione in alcuna cultura. Uno dei pochi motivi per usare StringComparison.InvariantCulture per il confronto è rendere persistenti i dati ordinati per una visualizzazione cross-culturalmente identica. Ad esempio, se un file di dati di grandi dimensioni che contiene un elenco di identificatori ordinati per la visualizzazione accompagna un'applicazione, l'aggiunta a questo elenco richiede un inserimento con ordinamento invariante.
Scegliere un membro StringComparison per la chiamata al metodo
Nella tabella seguente viene descritto il mapping dal contesto semantico di stringhe a un membro di enumerazione StringComparison.
Dati | Comportamento | System.StringComparison corrispondente valore |
---|---|---|
Identificatori interni sensibili alla distinzione tra maiuscole e minuscole. Identificatori con distinzione tra maiuscole e minuscole negli standard, ad esempio XML e HTTP. Impostazioni di sicurezza sensibili alle maiuscole. |
Identificatore non linguistico, in cui i byte corrispondono esattamente. | Ordinal |
Identificatori interni insensibili alle maiuscole. Identificatori senza distinzione tra maiuscole e minuscole negli standard, ad esempio XML e HTTP. Percorsi di file. Chiavi e valori del Registro di sistema. variabili di ambiente. Identificatori di risorsa (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 è irrilevante. | OrdinalIgnoreCase |
Alcuni dati persistenti e pertinenti in modo linguistico. Visualizzazione di dati linguistici che richiedono un ordinamento fisso. |
Dati indipendenti dalla cultura che sono ancora rilevanti in modo linguistico. | InvariantCulture oppure InvariantCultureIgnoreCase |
Dati visualizzati all'utente. La maggior parte degli input utente. |
Dati che richiedono usi linguistici locali. | CurrentCulture oppure CurrentCultureIgnoreCase |
Metodi comuni di confronto tra stringhe in .NET
Nelle sezioni seguenti vengono descritti i metodi più comunemente usati per il confronto tra stringhe.
Stringa.Confronta
Interpretazione predefinita: StringComparison.CurrentCulture.
Poiché è l'operazione più fondamentale nell'interpretazione delle stringhe, tutte le istanze di queste chiamate al metodo devono essere esaminate per determinare se le stringhe devono essere interpretate in base alla cultura corrente o dissociate dalla cultura (simbolicamente). In genere, è quest'ultimo e dovrebbe essere usato invece un StringComparison.Ordinal confronto.
La System.Globalization.CompareInfo classe , restituita dalla CultureInfo.CompareInfo proprietà , include anche un Compare metodo che fornisce un numero elevato di opzioni corrispondenti (ordinale, ignorando lo spazio vuoto, ignorando il tipo kana e così via) tramite l'enumerazione CompareOptions flag.
String.CompareTo
Interpretazione predefinita: StringComparison.CurrentCulture.
Questo metodo attualmente non offre un overload che specifichi un StringComparison tipo. In genere è possibile convertire questo metodo nel formato consigliato String.Compare(String, String, StringComparison) .
I tipi che implementano le IComparable interfacce e IComparable<T> implementano questo metodo. Poiché non offre l'opzione di un StringComparison parametro, le implementazioni dei tipi spesso consentono all'utente di specificare un StringComparer parametro nel loro costruttore. Nell'esempio seguente viene definita una FileName
classe il cui costruttore di classe include un StringComparer parametro . Questo StringComparer oggetto viene quindi utilizzato nel FileName.CompareTo
metodo .
class FileName : IComparable
{
private readonly StringComparer _comparer;
public string Name { get; }
public FileName(string name, StringComparer? comparer)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
Name = name;
if (comparer != null)
_comparer = comparer;
else
_comparer = StringComparer.OrdinalIgnoreCase;
}
public int CompareTo(object? obj)
{
if (obj == null) return 1;
if (obj is not FileName)
return _comparer.Compare(Name, obj.ToString());
else
return _comparer.Compare(Name, ((FileName)obj).Name);
}
}
Class FileName
Implements IComparable
Private ReadOnly _comparer As StringComparer
Public ReadOnly Property Name As String
Public Sub New(name As String, comparer As StringComparer)
If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))
Me.Name = name
If comparer IsNot Nothing Then
_comparer = comparer
Else
_comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
If TypeOf obj IsNot FileName Then
Return _comparer.Compare(Name, obj.ToString())
Else
Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
End If
End Function
End Class
String.Equals (metodo)
Interpretazione predefinita: StringComparison.Ordinal.
La String classe consente di verificare l'uguaglianza chiamando gli overload del metodo statico o dell'istanza Equals oppure usando l'operatore di uguaglianza statica. Gli sovraccarichi e l'operatore usano il confronto ordinale per impostazione predefinita. Tuttavia, è comunque consigliabile chiamare un overload che specifichi in modo esplicito il StringComparison tipo anche se si vuole eseguire un confronto ordinale. In questo modo è più semplice cercare il codice per una determinata interpretazione di stringa.
String.ToUpper e String.ToLower
Interpretazione predefinita: StringComparison.CurrentCulture.
Prestare attenzione quando si usano i metodi String.ToUpper() e String.ToLower(), poiché forzare una stringa in maiuscolo o minuscolo viene spesso usato come una piccola normalizzazione per confrontare le stringhe indipendentemente dalle maiuscole e minuscole. In tal caso, prendere in considerazione l'uso di un confronto senza distinzione tra maiuscole e minuscole.
Sono disponibili anche i String.ToUpperInvariant metodi e String.ToLowerInvariant . ToUpperInvariant è il modo standard per normalizzare il caso. I confronti effettuati con StringComparison.OrdinalIgnoreCase sono la composizione comportamentale di due chiamate: la chiamata ToUpperInvariant a entrambi gli argomenti stringa e l'esecuzione di un confronto usando StringComparison.Ordinal.
Sono disponibili anche sovraccarichi per la conversione in lettere maiuscole e minuscole in una cultura specifica, passando un oggetto CultureInfo che rappresenta tale 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 questi metodi eseguono un confronto sensibile alla cultura. In particolare, possono ignorare i caratteri non stampati.
String.IndexOf e String.LastIndexOf
Interpretazione predefinita: StringComparison.CurrentCulture.
C'è una mancanza di coerenza nel modo in cui gli overload predefiniti di questi metodi eseguono le comparazioni. 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 sensibile alle impostazioni culturali.
Se chiami il metodo String.IndexOf(String) o String.LastIndexOf(String) e passi una stringa da individuare nell'istanza corrente, consigliamo di chiamare un overload che specifichi esplicitamente il tipo StringComparison. Gli overload che includono un Char argomento non consentono di specificare un StringComparison tipo.
Metodi che eseguono il confronto di stringhe indirettamente
Alcuni metodi non stringa con confronto tra stringhe come operazione centrale usano il StringComparer tipo . La StringComparer classe include sei proprietà statiche che restituiscono StringComparer istanze i cui StringComparer.Compare metodi eseguono i tipi di confronto tra stringhe seguenti:
- Confronti di stringhe sensibili alla cultura corrente. Questo StringComparer oggetto viene restituito dalla StringComparer.CurrentCulture proprietà .
- Confronti senza distinzione tra maiuscole e minuscole usando la cultura corrente. Questo StringComparer oggetto viene restituito dalla StringComparer.CurrentCultureIgnoreCase proprietà .
- Confronti senza distinzione tra impostazioni cultura usando le regole di confronto delle parole delle impostazioni cultura invarianti. Questo StringComparer oggetto viene restituito dalla StringComparer.InvariantCulture proprietà .
- Confronti senza distinzione tra maiuscole e minuscole né di cultura utilizzando le regole di confronto delle parole della cultura invariante. Questo StringComparer oggetto viene restituito dalla StringComparer.InvariantCultureIgnoreCase proprietà .
- Confronto ordinale. Questo StringComparer oggetto viene restituito dalla StringComparer.Ordinal proprietà .
- Confronto ordinale insensibile alle maiuscole e minuscole. Questo StringComparer oggetto viene restituito dalla StringComparer.OrdinalIgnoreCase proprietà .
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 in una raccolta, il cambio delle impostazioni cultura correnti può invalidare gli invarianti nella raccolta. Il Array.BinarySearch metodo presuppone che gli elementi nella matrice in cui eseguire la ricerca siano già ordinati. Per ordinare qualsiasi elemento stringa nella matrice, il Array.Sort metodo chiama il String.Compare metodo per ordinare singoli elementi. L'uso di un operatore di confronto sensibile alle impostazioni cultura può essere pericoloso se le impostazioni cultura cambiano tra il momento in cui la matrice viene ordinata e il relativo contenuto vengono cercati. Nel codice seguente, ad esempio, l'archiviazione e il recupero operano sull'operatore di confronto fornito in modo implicito dalla Thread.CurrentThread.CurrentCulture
proprietà . Se le impostazioni della cultura possono cambiare tra le chiamate a StoreNames
e DoesNameExist
, e soprattutto se i contenuti dell'array sono memorizzati tra le due chiamate al metodo, la ricerca binaria potrebbe non riuscire.
// Incorrect
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function
Nell'esempio seguente viene visualizzata una variante consigliata, che usa lo stesso metodo di confronto ordinale (senza distinzione delle impostazioni cultura) sia per ordinare che per eseguire la ricerca nella matrice. Il codice di modifica si riflette nelle righe etichettate Line A
e Line B
nei due esempi.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function
Se questi dati vengono salvati in modo permanente e spostati tra impostazioni cultura e l'ordinamento viene usato per presentare questi dati all'utente, è consigliabile usare StringComparison.InvariantCulture, che opera in modo linguistico per un output utente migliore, ma non è influenzato dalle modifiche apportate alle impostazioni cultura. L'esempio seguente modifica i due esempi precedenti per usare le impostazioni cultura invarianti per l'ordinamento e la ricerca nella matrice.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function
Esempio di raccolte: costruttore Hashtable
Le stringhe di hashing forniscono un secondo esempio di un'operazione interessata dal modo in cui vengono confrontate le stringhe.
Nell'esempio seguente viene creata un'istanza dell'oggetto Hashtable passandogli l'oggetto StringComparer restituito dalla proprietà StringComparer.OrdinalIgnoreCase. Poiché una classe StringComparer derivata da StringComparer implementa l'interfaccia IEqualityComparer , il relativo GetHashCode metodo viene usato per calcolare il codice hash delle stringhe nella tabella hash.
using System.IO;
using System.Collections;
const int InitialCapacity = 100;
Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();
// Fill the hash table
PopulateFileTable(directoryToProcess);
// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
PrintCreationTime(file.ToUpper());
void PopulateFileTable(string directory)
{
foreach (string file in Directory.GetFiles(directory))
creationTimeByFile.Add(file, File.GetCreationTime(file));
}
void PrintCreationTime(string targetFile)
{
object? dt = creationTimeByFile[targetFile];
if (dt is DateTime value)
Console.WriteLine($"File {targetFile} was created at time {value}.");
else
Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO
Module Program
Const InitialCapacity As Integer = 100
Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()
Sub Main()
' Fill the hash table
PopulateFileTable(s_directoryToProcess)
' Get some of the files and try to find them with upper cased names
For Each File As String In Directory.GetFiles(s_directoryToProcess)
PrintCreationTime(File.ToUpper())
Next
End Sub
Sub PopulateFileTable(directoryPath As String)
For Each file As String In Directory.GetFiles(directoryPath)
s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
Next
End Sub
Sub PrintCreationTime(targetFile As String)
Dim dt As Object = s_creationTimeByFile(targetFile)
If TypeOf dt Is Date Then
Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
Else
Console.WriteLine($"File {targetFile} does not exist.")
End If
End Sub
End Module