Come confrontare stringhe in C#
Il confronto delle stringhe viene eseguito per rispondere a una delle due domande seguenti: "Queste due stringhe sono uguali?" oppure "In che ordine vanno disposte queste stringhe quando si esegue l'ordinamento?".
Queste due domande vengono complicate da fattori propri dei confronti fra stringhe:
- È possibile scegliere un confronto ordinale o linguistico.
- È possibile scegliere se la distinzione maiuscole/minuscole è importante.
- È possibile scegliere confronti specifici in base alle impostazioni cultura.
- I confronti linguistici dipendono dalle impostazioni cultura e dalla piattaforma.
I campi di enumerazione System.StringComparison rappresentano queste opzioni:
- CurrentCulture: le stringhe vengono confrontate usando regole di ordinamento dipendenti dalle impostazioni cultura e le impostazioni cultura correnti.
- CurrentCultureIgnoreCase: le stringhe vengono confrontate usando regole di ordinamento dipendenti dalle impostazioni cultura e le impostazioni cultura correnti e ignorando la distinzione tra maiuscole e minuscole nelle stringhe da confrontare.
- InvariantCulture: le stringhe vengono confrontate usando regole di ordinamento dipendenti dalle impostazioni cultura e le impostazioni cultura correnti.
- InvariantCultureIgnoreCase: le stringhe vengono confrontate usando regole di ordinamento dipendenti dalle impostazioni cultura e le impostazioni cultura correnti e ignorando la distinzione tra maiuscole e minuscole nelle stringhe da confrontare.
- Ordinal: le stringhe vengono confrontate usando regole di ordinamento ordinali (binarie).
- OrdinalIgnoreCase: le stringhe vengono confrontate usando regole di ordinamento ordinali (binarie) e ignorando la distinzione tra maiuscole e minuscole nelle stringhe da confrontare.
Nota
Gli esempi in C# in questo articolo vengono eseguiti nello strumento di esecuzione e playground per codice inline Try.NET. Selezionare il pulsante Esegui per eseguire un esempio in una finestra interattiva. Dopo aver eseguito il codice, è possibile modificarlo ed eseguire il codice modificato selezionando di nuovo Esegui. Il codice modificato viene eseguito nella finestra interattiva o, se la compilazione non riesce, la finestra interattiva visualizza tutti i messaggi di errore del compilatore C#.
Quando si confrontano le stringhe, si definisce un ordine tra di esse. I confronti vengono usati per ordinare una sequenza di stringhe. Quando la sequenza è in un ordine noto, l'esecuzione di una ricerca risulta più semplice sia per gli utenti che per il software. Altri confronti possono verificare se le stringhe sono uguali. Questi controlli di similitudine sono simili alla verifica di uguaglianza, ma è possibile che alcune differenze, quali l'uso di maiuscole e minuscole, vengano ignorate.
Confronti ordinali predefiniti
Per impostazione predefinita, le operazioni più comuni:
- String.Equals
- String.Equality e String.Inequality, ovvero gli operatori di uguaglianza
==
e!=
, eseguono rispettivamente un confronto ordinale con distinzione tra maiuscole e minuscole. String.Equals include un overload in cui è possibile specificare un argomento StringComparison per modificarne le regole di ordinamento. L'esempio seguente lo dimostra:
string root = @"C:\users";
string root2 = @"C:\Users";
bool result = root.Equals(root2);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
result = root.Equals(root2, StringComparison.Ordinal);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not equal")}");
Il confronto ordinale predefinito non tiene conto delle regole linguistiche durante il confronto di stringhe. ma viene confrontato il valore binario di ogni oggetto Char nelle due stringhe. Di conseguenza, anche il confronto ordinale predefinito prevede la distinzione tra maiuscole e minuscole.
Il test di uguaglianza con String.Equals e gli operatori ==
e !=
differisce dal confronto di stringhe che usa i metodi String.CompareTo e Compare(String, String). Eseguono tutti un confronto con distinzione maiuscole/minuscole. Tuttavia, mentre i test di uguaglianza eseguono un confronto ordinale, i metodi CompareTo
e Compare
eseguono un confronto linguistico consapevole della cultura corrente. Rendere chiara la finalità del codice chiamando un overload che specifichi in modo esplicito il tipo di confronto da eseguire.
Confronti ordinali senza distinzione tra maiuscole e minuscole
Il metodo String.Equals(String, StringComparison) consente di specificare un valore StringComparison pari a StringComparison.OrdinalIgnoreCase per impostare un confronto ordinale senza distinzione maiuscole/minuscole. È anche disponibile un metodo String.Compare(String, String, StringComparison) statico che esegue un confronto ordinale senza distinzione tra maiuscole e minuscole, se si specifica il valore StringComparison.OrdinalIgnoreCase per l'argomento StringComparison. Questi confronti vengono illustrati nel codice seguente:
string root = @"C:\users";
string root2 = @"C:\Users";
bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);
bool areEqual = String.Equals(root, root2, StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType: StringComparison.OrdinalIgnoreCase);
Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not equal.")}");
if (comparison < 0)
Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
Console.WriteLine($"<{root}> is greater than <{root2}>");
else
Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");
Quando si esegue un confronto ordinale senza distinzione tra maiuscole e minuscole, questi metodi usano le convenzioni di combinazione di maiuscole e minuscole delle impostazioni cultura inglese non dipendenti da paese/area geografica.
Confronti linguistici
Molti metodi di confronto di stringhe (ad esempioString.StartsWith), usano regole linguistiche per le impostazioni cultura correnti per impostazione predefinita per ordinare i relativi input. Questo confronto linguistico è detto anche "ordinamento per parola". Quando si esegue un confronto linguistico, è possibile che a determinati caratteri Unicode non alfanumerici venga assegnata una valenza specifica. Ad esempio il trattino "-" può avere una valenza ridotta, pertanto le parole "co-op"e "coop" vengono visualizzate l'una accanto all'altra nell'ordinamento. Alcuni caratteri di controllo non stampabili potrebbero essere ignorati. Alcuni caratteri Unicode potrebbero anche essere equivalenti a una sequenza di istanze di Char. L'esempio seguente usa la frase "Ballano per strada."in tedesco con la "ss" (U+0073 U+0073) in una stringa e la 'ß' (U+00DF) in un'altra. Dal punto di vista linguistico (in Windows), "ss" equivale al carattere tedesco Esszet "ß", sia nelle impostazioni cultura "en-US" che in quelle "de-DE".
string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";
Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");
bool equal = String.Equals(first, second, StringComparison.InvariantCulture);
Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")} equal.");
showComparison(first, second);
string word = "coop";
string words = "co-op";
string other = "cop";
showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}
In Windows, prima di .NET 5, l'ordinamento di "cop", "coop" e "co-op" cambia quando si passa da un confronto linguistico a un confronto ordinale. Anche l'ordinamento delle due frasi in tedesco cambia a seconda del tipo di confronto. Prima di .NET 5, le API di globalizzazione di .NET usavano le librerie National Language Support (NLS). In .NET 5 e versioni successive, le API di globalizzazione di .NET usano le librerie International Components for Unicode (ICU), che unificano il comportamento di globalizzazione di .NET in tutti i sistemi operativi supportati.
Confronti che usano impostazioni cultura specifiche
Nell'esempio seguente vengono archiviati oggetti CultureInfo per le impostazioni cultura en-US e de-DE. I confronti vengono eseguiti usando un oggetto CultureInfo per garantire un confronto dipendente dalle impostazioni cultura. Le impostazioni cultura usate hanno effetto sui confronti linguistici. L'esempio seguente visualizza i risultati del confronto tra le due frasi in lingua tedesca usando le impostazioni cultura "en-US" e le impostazioni cultura "de-DE":
string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";
Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");
var en = new System.Globalization.CultureInfo("en-US");
// For culture-sensitive comparisons, use the String.Compare
// overload that takes a StringComparison value.
int i = String.Compare(first, second, en, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");
var de = new System.Globalization.CultureInfo("de-DE");
i = String.Compare(first, second, de, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");
bool b = String.Equals(first, second, StringComparison.CurrentCulture);
Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");
string word = "coop";
string words = "co-op";
string other = "cop";
showComparison(word, words, en);
showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo culture)
{
int compareLinguistic = String.Compare(one, two, en, System.Globalization.CompareOptions.None);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using en-US culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using en-US culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using en-US culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}
In genere i confronti con rilevamento delle impostazioni cultura vengono usati per confrontare e ordinare stringhe immesse dagli utenti. I caratteri e le convenzioni di ordinamento di queste stringhe possono variare a seconda delle impostazioni locali del computer dell'utente. Anche stringhe che contengono caratteri identici potrebbero essere ordinate in modo diverso a seconda delle impostazioni cultura del thread corrente.
Ordinamento linguistico e ricerca di stringhe nelle matrici
Gli esempi seguenti illustrano come eseguire l'ordinamento e la ricerca di stringhe in una matrice mediante un confronto linguistico dipendente dalle impostazioni cultura correnti. Si usano i metodi Array statici che accettano un parametro System.StringComparer.
L'esempio seguente illustra come ordinare una matrice di stringhe in base alle impostazioni cultura correnti:
string[] lines = new string[]
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
// Specify Ordinal to demonstrate the different behavior.
Array.Sort(lines, StringComparer.CurrentCulture);
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Quando la matrice è ordinata, è possibile cercare voci con una ricerca binaria. Una ricerca binaria inizia a metà della raccolta per determinare quale metà contiene la stringa cercata. Ogni confronto successivo divide a metà la parte rimanente della raccolta. La matrice viene ordinata usando StringComparer.CurrentCulture. La funzione locale ShowWhere
visualizza informazioni sul punto in cui è stata trovata la stringa. Se la stringa non è stata trovata, il valore restituito indica dove si troverebbe se fosse stata trovata.
string[] lines = new string[]
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);
string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);
Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");
void ShowWhere<T>(T[] array, int index)
{
if (index < 0)
{
index = ~index;
Console.Write("Not found. Sorts between: ");
if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{array[index - 1]} and ");
if (index == array.Length)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{array[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}
Ordinamento e ricerca ordinali nelle raccolte
Il codice seguente usa la classe della raccolta System.Collections.Generic.List<T> per memorizzare le stringhe. Le stringhe vengono ordinate usando il metodo List<T>.Sort. Questo metodo richiede un delegato che confronta e ordina due stringhe. La funzione di confronto è resa disponibile dal metodo String.CompareTo. Eseguire l'esempio e osservare l'ordine. Questa operazione di ordinamento usa un ordinamento ordinale con distinzione maiuscole/minuscole. Per specificare regole di confronto diverse sarebbe necessario usare i metodi String.Compare statici.
List<string> lines = new List<string>
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
lines.Sort((left, right) => left.CompareTo(right));
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Dopo l'ordinamento è possibile eseguire ricerche nell'elenco di stringhe usando una ricerca binaria. L'esempio seguente mostra come eseguire una ricerca nell'elenco ordinato utilizzando la stessa funzione di confronto. La funzione locale ShowWhere
visualizza il punto in cui si trova o dovrebbe trovarsi il testo cercato:
List<string> lines = new List<string>
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));
string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);
Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");
void ShowWhere<T>(IList<T> collection, int index)
{
if (index < 0)
{
index = ~index;
Console.Write("Not found. Sorts between: ");
if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{collection[index - 1]} and ");
if (index == collection.Count)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{collection[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}
Assicurarsi di usare sempre lo stesso tipo di confronto per l'ordinamento e la ricerca. L'uso di tipi di confronto diversi per l'ordinamento e la ricerca produce risultati imprevisti.
Le classi Collection, ad esempio System.Collections.Hashtable, System.Collections.Generic.Dictionary<TKey,TValue>, e System.Collections.Generic.List<T> contengono costruttori che accettano un parametro System.StringComparer quando il tipo degli elementi o delle chiavi è string
. In generale è necessario usare sempre questi costruttori, quando possibile, e specificare il parametro StringComparer.Ordinal o StringComparer.OrdinalIgnoreCase.