Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
.NET offre une prise en charge étendue du développement d’applications localisées et globalisées, et facilite l’application des conventions de la culture actuelle ou d’une culture spécifique lors de l’exécution d’opérations courantes telles que le tri et l’affichage de chaînes. Toutefois, le tri ou la comparaison de chaînes n'est pas toujours une opération dépendante de la culture. Par exemple, les chaînes utilisées en interne par une application doivent généralement être gérées de manière identique dans toutes les cultures. Lorsque des données de chaîne indépendantes culturellement, telles que des balises XML, des balises HTML, des noms d’utilisateur, des chemins d’accès aux fichiers et les noms d’objets système, sont interprétées comme si elles étaient sensibles à la culture, le code d’application peut être soumis à des bogues subtils, à des performances médiocres et, dans certains cas, à des problèmes de sécurité.
Cet article examine les méthodes de tri, de comparaison et de casse de chaîne dans .NET, présente des recommandations pour sélectionner une méthode de gestion de chaîne appropriée et fournit des informations supplémentaires sur les méthodes de gestion des chaînes.
Recommandations relatives à l’utilisation de chaînes
Lorsque vous développez avec .NET, suivez ces recommandations lorsque vous comparez des chaînes.
Conseil / Astuce
Différentes méthodes liées à des chaînes effectuent une comparaison. Exemples : String.Equals, String.Compare, String.IndexOfet String.StartsWith.
- Utilisez des surcharges qui spécifient explicitement les règles de comparaison de chaînes pour les opérations de chaînes. En règle générale, cela implique l’appel d’une surcharge de méthode qui a un paramètre de type StringComparison.
- Utilisez StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase pour les comparaisons comme valeur par défaut sécurisée pour la correspondance de chaînes de culture non spécifiée.
- Utilisez des comparaisons avec StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase pour de meilleures performances.
- Utilisez des opérations de chaîne basées sur StringComparison.CurrentCulture lors de l’affichage de la sortie à l’utilisateur.
- Utilisez les valeurs non linguistiques StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase au lieu d'opérations sur les chaînes basées sur CultureInfo.InvariantCulture lorsque la comparaison n'a pas de pertinence linguistique (par exemple, symbolique).
- Utilisez la String.ToUpperInvariant méthode au lieu de la String.ToLowerInvariant méthode lorsque vous normalisez des chaînes pour la comparaison.
- Utilisez une surcharge de la String.Equals méthode pour tester si deux chaînes sont égales.
- Utilisez les méthodes String.Compare et String.CompareTo pour trier les chaînes, non pour vérifier l’égalité.
- Utilisez la mise en forme sensible à la culture pour afficher des données non-chaînes, telles que des nombres et des dates, dans une interface utilisateur. Utilisez le formatage avec la culture invariante pour conserver des données non textuelles sous forme textuelle.
Évitez les pratiques suivantes lorsque vous comparez des chaînes de caractères :
- N'utilisez pas de surcharges qui ne spécifient pas explicitement ou implicitement les règles de comparaison de chaînes pour les opérations de chaînes.
- N’utilisez pas d’opérations de chaîne basées sur StringComparison.InvariantCulture dans la plupart des cas. L’une des rares exceptions est que vous conservez des données linguistiquement significatives mais indépendantes culturellement.
- N'utilisez pas de surcharge des méthodes String.Compare ou CompareTo et testez si la valeur de retour est zéro pour déterminer si deux chaînes sont égales.
Spécifier explicitement les comparaisons de chaînes
La plupart des méthodes de manipulation de chaînes dans .NET sont surchargées. En règle générale, une ou plusieurs surcharges acceptent les paramètres par défaut, tandis que d’autres n’acceptent aucune valeur par défaut et définissent plutôt la façon précise dont les chaînes doivent être comparées ou manipulées. La plupart des méthodes qui ne reposent pas sur les valeurs par défaut incluent un paramètre de type StringComparison, qui est une énumération qui spécifie explicitement les règles de comparaison de chaînes par culture et cas. Le tableau suivant décrit les membres de l'énumération StringComparison .
Membre StringComparison | Descriptif |
---|---|
CurrentCulture | Effectue une comparaison respectant la casse à l'aide de la culture actuelle. |
CurrentCultureIgnoreCase | Effectue une comparaison ne respectant pas la casse à l'aide de la culture actuelle. |
InvariantCulture | Effectue une comparaison respectant la casse à l'aide de la culture dite indifférente. |
InvariantCultureIgnoreCase | Effectue une comparaison ne respectant pas la casse à l'aide de la culture dite indifférente. |
Ordinal | Effectue une comparaison ordinale. |
OrdinalIgnoreCase | Effectue une comparaison ordinale ne respectant pas la casse. |
Par exemple, la IndexOf méthode, qui retourne l’index d’une sous-chaîne dans un String objet qui correspond à un caractère ou une chaîne, a neuf surcharges :
- IndexOf(Char), IndexOf(Char, Int32)et IndexOf(Char, Int32, Int32), qui effectuent par défaut une recherche ordinale (respectant la casse et indépendante de la culture) d'un caractère dans la chaîne.
- IndexOf(String), IndexOf(String, Int32)et IndexOf(String, Int32, Int32), qui effectuent par défaut une recherche respectant la casse et dépendante de la culture d'une sous-chaîne dans la chaîne.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)et IndexOf(String, Int32, Int32, StringComparison), qui incluent un paramètre de type StringComparison qui permet la forme de la comparaison à spécifier.
Nous vous recommandons de sélectionner une surcharge qui n’utilise pas les valeurs par défaut, pour les raisons suivantes :
Certaines surcharges ayant des paramètres par défaut (celles qui recherchent un Char dans l'instance de chaîne) effectuent une comparaison ordinale, tandis que d'autres (celles qui recherchent une chaîne dans l'instance de chaîne) sont dépendantes de la culture. Il est difficile de se rappeler quelle méthode utilise la valeur par défaut et facile à confondre les surcharges.
L’intention du code qui s’appuie sur des valeurs par défaut pour les appels de méthode n’est pas claire. Dans l’exemple suivant, qui est basé sur des valeurs par défaut, il est difficile de savoir si le développeur voulait en fait une comparaison ordinale ou linguistique de deux chaînes, ou si une différence de casse entre
url.Scheme
et "https" peut entraîner le retour defalse
.Uri url = new("https://learn.microsoft.com/"); // Incorrect if (string.Equals(url.Scheme, "https")) { // ...Code to handle HTTPS protocol. }
Dim url As New Uri("https://learn.microsoft.com/") ' Incorrect If String.Equals(url.Scheme, "https") Then ' ...Code to handle HTTPS protocol. End If
En général, nous vous recommandons d’appeler une méthode qui ne s’appuie pas sur les valeurs par défaut, car elle rend l’intention du code non ambiguë. Cela rend le code plus lisible et plus facile à déboguer et à gérer. L’exemple suivant aborde les questions soulevées sur l’exemple précédent. Il indique clairement que la comparaison ordinale est utilisée et que les différences dans le cas sont ignorées.
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
Détails de la comparaison de chaînes
La comparaison de chaînes est le cœur de nombreuses opérations liées aux chaînes, en particulier le tri et le test d'égalité. Les chaînes sont triées dans un ordre déterminé : si et seulement si « my » apparaît avant « string » dans une liste triée de chaînes, « my » doit être considérée comme inférieure ou égale à « string ». En outre, la comparaison définit implicitement l’égalité. L’opération de comparaison retourne zéro pour les chaînes qu’elle considère comme égales. Une bonne interprétation est que aucune chaîne n’est inférieure à l’autre. La plupart des opérations significatives impliquant des chaînes incluent une ou les deux procédures suivantes : comparaison avec une autre chaîne et exécution d’une opération de tri bien définie.
Remarque
Vous pouvez télécharger les tables de pondération de tri, un ensemble de fichiers texte qui contiennent des informations sur les pondérations de caractères utilisées dans les opérations de tri et de comparaison pour les systèmes d’exploitation Windows, ainsi que la table d’éléments de classement Unicode par défaut, la dernière version de la table de pondération de tri pour Linux et macOS. La version spécifique de la table de pondération de tri sur Linux et macOS dépend de la version des composants internationaux pour les bibliothèques Unicode installées sur le système. Pour plus d’informations sur les versions d’ICU et les versions Unicode qu’ils implémentent, consultez Téléchargement d’ICU.
Toutefois, l’évaluation de deux chaînes pour l’égalité ou l’ordre de tri ne génère pas de résultat unique et correct ; le résultat dépend des critères utilisés pour comparer les chaînes. En particulier, les comparaisons de chaînes qui sont ordinales ou basées sur les conventions de casse et de tri de la culture actuelle ou la culture invariante (culture aux paramètres régionaux non spécifiés basée sur la langue anglaise) peuvent produire des résultats différents.
En outre, les comparaisons de chaînes utilisant différentes versions de .NET ou .NET sur différents systèmes d’exploitation ou versions de système d’exploitation peuvent retourner des résultats différents. Pour plus d’informations, consultez Chaînes et Norme Unicode.
Comparaisons de chaînes qui utilisent la culture actuelle
L'un des critères à prendre en compte est l'utilisation des conventions de la culture actuelle lors de la comparaison de chaînes. Les comparaisons basées sur la culture actuelle utilisent la culture ou les paramètres régionaux actuels du thread. Si la culture n’est pas définie par l’utilisateur, elle est définie par défaut sur le paramètre du système d’exploitation. Vous devez toujours utiliser des comparaisons basées sur la culture actuelle lorsque les données sont linguistiquesment pertinentes et lorsqu’elles reflètent l’interaction de l’utilisateur sensible à la culture.
Toutefois, le comportement de la comparaison et de la casse dans .NET change en fonction de la culture. Cela se produit lorsqu’une application s’exécute sur un ordinateur qui a une culture différente de celle de l’ordinateur sur lequel l’application a été développée, ou lorsque le thread en cours d’exécution modifie sa culture. Ce comportement est intentionnel, mais il reste non évident pour de nombreux développeurs. L’exemple suivant illustre les différences dans l’ordre de tri entre les cultures américaines anglais («en-US») et suédois («sv-SE»). Notez que les mots « ångström », « Windows » et « Visual Studio » apparaissent dans différentes positions dans les tableaux de chaînes triés.
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
Les comparaisons ne respectant pas la casse qui utilisent la culture actuelle sont identiques aux comparaisons dépendantes de la culture, mais elles ignorent la casse, comme défini par la culture actuelle du thread. Ce comportement peut également se manifester dans les ordres de tri.
Les comparaisons qui utilisent la sémantique de culture actuelle sont la valeur par défaut pour les méthodes suivantes :
- Les surcharges String.Compare qui n'incluent pas de paramètre StringComparison.
- surcharges String.CompareTo.
- Méthode par défaut String.StartsWith(String) et String.StartsWith(String, Boolean, CultureInfo) méthode avec un
null
CultureInfo paramètre. - Méthode par défaut String.EndsWith(String) et String.EndsWith(String, Boolean, CultureInfo) méthode avec un
null
CultureInfo paramètre. - Les surchargesString.IndexOf qui acceptent un String comme paramètre de recherche et qui n'ont pas de paramètre StringComparison ;
- Les surchargesString.LastIndexOf qui acceptent un String comme paramètre de recherche et qui n'ont pas de paramètre StringComparison ;
Dans tous les cas, nous vous recommandons d'appeler une surcharge qui a un paramètre StringComparison , afin que l'objectif de l'appel de la méthode soit clair.
Les bogues subtils et pas si subtils peuvent apparaître lorsque les données de chaîne non linguistiques sont interprétées linguistiquement, ou lorsque les données de chaîne d’une culture particulière sont interprétées à l’aide des conventions d’une autre culture. L’exemple canonique est le problème Turkish-I.
Pour presque tous les alphabets latins, y compris l’anglais américain, le caractère « i » (\u0069) est la version minuscule du caractère « I » (\u0049). Cette règle de casse devient rapidement la valeur par défaut pour quelqu'un qui effectue de la programmation dans une telle culture. Toutefois, l’alphabet turc ( »tr-TR« ) comprend un caractère « I avec un point » « İ » (\u0130), qui est la version majuscule de « i ». Turc comprend également un caractère minuscule « i sans point », « ı » (\u0131), qui capitalise sur « I ». Ce comportement se produit également dans la culture azerbaïdjan (« az »).
Par conséquent, les hypothèses faites au sujet de la capitalisation du « i » ou de la mise en minuscule du « I » ne sont pas valides dans toutes les cultures. Si vous utilisez les surcharges par défaut pour les routines de comparaison de chaînes, elles sont soumises à une variance entre les cultures. Si les données à comparer sont non linguistiques, l'utilisation de surcharges par défaut peut produire des résultats indésirables, comme le montre la tentative suivante d'exécution d'une comparaison ne respectant pas la casse des chaînes "bill" et "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
Cette comparaison peut entraîner des problèmes significatifs si la culture est utilisée par inadvertance dans les paramètres sensibles à la sécurité, comme dans l’exemple suivant. Un appel de méthode comme IsFileURI("file:")
renvoie true
si la culture actuelle est l’anglais américain, mais false
si la culture actuelle est turque. Par conséquent, sur les systèmes turcs, quelqu'un pourrait contourner les mesures de sécurité qui bloquent l'accès aux URI ne respectant pas la casse qui commencent par "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
Dans ce cas, étant donné que « file : » est destiné à être interprété comme un identificateur non linguistique et non sensible à la culture, le code doit être écrit comme indiqué dans l’exemple suivant :
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
Opérations de chaîne ordinale
Spécifier la valeur StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase dans un appel de méthode signifie une comparaison non linguistique dans laquelle les caractéristiques des langues naturelles sont ignorées. Les méthodes qui sont appelées avec ces valeurs StringComparison font reposer les décisions d'opération de chaîne sur de simples comparaisons d'octets plutôt que sur la casse ou des tables d'équivalences paramétrables par la culture. Dans la plupart des cas, cette approche convient le mieux à l’interprétation prévue des chaînes tout en rendant le code plus rapide et plus fiable.
Les comparaisons ordinales sont des comparaisons de chaînes dans lesquelles chaque octet de chaque chaîne est comparé sans interprétation linguistique ; par exemple, « windows » ne correspond pas à « Windows ». Il s’agit essentiellement d’un appel à la fonction runtime strcmp
C. Utilisez cette comparaison quand le contexte indique que les chaînes doivent correspondre exactement ou demande une stratégie de correspondance classique. En outre, la comparaison ordinale est l’opération de comparaison la plus rapide, car elle n’applique aucune règle linguistique lors de la détermination d’un résultat.
Les chaînes dans .NET peuvent contenir des caractères Null incorporés (et d’autres caractères non imprimables). L’une des différences les plus claires entre la comparaison ordinale et sensible à la culture (y compris les comparaisons qui utilisent la culture invariante) concerne la gestion des caractères Null incorporés dans une chaîne. Ces caractères sont ignorés lorsque vous utilisez les méthodes String.Compare et String.Equals pour effectuer des comparaisons sensibles à la culture (y compris les comparaisons qui utilisent la culture invariante). Par conséquent, les chaînes qui contiennent des caractères null incorporés peuvent être considérées comme égales aux chaînes qui ne le font pas. Les caractères non imprimables incorporés peuvent être ignorés à des fins de méthodes de comparaison de chaînes, telles que String.StartsWith.
Importante
Bien que les méthodes de comparaison de chaînes ignorent les caractères null incorporés, les méthodes de recherche de chaîne telles que String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfet String.StartsWith ne le font pas.
L’exemple suivant effectue une comparaison sensible à la culture de la chaîne « Aa » avec une chaîne similaire qui contient plusieurs caractères null incorporés entre « A » et « a », et montre comment les deux chaînes sont considérées comme égales :
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
Toutefois, les chaînes ne sont pas considérées comme égales lorsque vous utilisez la comparaison ordinale, comme l’illustre l’exemple suivant :
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
Les comparaisons ordinales ne respectant pas la casse sont l'approche la plus conservatrice suivante. Ces comparaisons ignorent la plus grande partie de la casse ; par exemple, "windows" correspond à "Windows". Lors du traitement de caractères ASCII, cette stratégie est équivalente à StringComparison.Ordinal, excepté qu'elle ignore la casse ASCII habituelle. Par conséquent, tout caractère dans [A, Z] (\u0041-\u005A) correspond au caractère correspondant dans [a,z] (\u0061-\007A). La casse hors de la plage ASCII utilise les tables de la culture dite indifférente. Par conséquent, la comparaison suivante :
string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
équivaut à (mais plus rapidement que) à cette comparaison :
string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)
Ces comparaisons sont encore très rapides.
Les deux StringComparison.Ordinal et StringComparison.OrdinalIgnoreCase utilisent directement les valeurs binaires et sont les mieux adaptés pour la correspondance. Lorsque vous n’êtes pas sûr de vos paramètres de comparaison, utilisez l’une de ces deux valeurs. Toutefois, étant donné qu’ils effectuent une comparaison d’octets par octets, ils ne trient pas par ordre de tri linguistique (comme un dictionnaire anglais) mais par un ordre de tri binaire. Les résultats peuvent sembler étranges dans la plupart des contextes s’ils sont affichés aux utilisateurs.
La sémantique ordinale est la valeur par défaut pour les surcharges de String.Equals qui n’incluent pas d’argument StringComparison (notamment l’opérateur d’égalité). Dans tous les cas, nous vous recommandons d'appeler une surcharge ayant un paramètre StringComparison .
Opérations de chaîne qui utilisent la culture invariante
Les comparaisons avec la culture invariante utilisent la CompareInfo propriété retournée par la propriété statique CultureInfo.InvariantCulture . Ce comportement est le même sur tous les systèmes ; il traduit tous les caractères en dehors de sa plage en ce qu’il croit être des caractères invariants équivalents. Cette stratégie peut être utile pour maintenir un ensemble de comportements de chaîne entre les cultures, mais elle fournit souvent des résultats inattendus.
Les comparaisons ne respectant pas la casse avec la culture dite indifférente utilisent également la propriété CompareInfo statique retournée également par la propriété CultureInfo.InvariantCulture statique pour les informations de comparaison. Toutes les différences de cas entre ces caractères traduits sont ignorées.
Comparaisons qui utilisent StringComparison.InvariantCulture et StringComparison.Ordinal fonctionnent de façon identique sur des chaînes ASCII. Toutefois, StringComparison.InvariantCulture prend des décisions linguistiques qui peuvent ne pas être appropriées pour les chaînes qui doivent être interprétées comme un ensemble d’octets. L’objet CultureInfo.InvariantCulture.CompareInfo
fait que la méthode Compare interprète certains jeux de caractères comme équivalents. Par exemple, l’équivalence suivante est valide dans la culture invariante :
InvariantCulture : a + ̊ = å
Le caractère LETTRE MINUSCULE LATINE A "a" (\u0061), quand il se trouve à côté du caractère DIACRITIQUE ROND EN CHEF " + " ̊" (\u030a), est interprété comme une LETTRE MINUSCULE LATINE A AVEC DIACRITIQUE ROND EN CHEF "å" (\u00e5). Comme l’illustre l’exemple suivant, ce comportement diffère de la comparaison 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
Lors de l’interprétation des noms de fichiers, des cookies ou tout autre élément où une combinaison telle que « å » peut apparaître, les comparaisons ordinales offrent toujours le comportement le plus transparent et le plus adapté.
En équilibre, la culture invariante a peu de propriétés qui le rendent utile pour la comparaison. Elle fait une comparaison d’une manière linguistiquement pertinente, ce qui l’empêche de garantir une équivalence symbolique complète, mais ce n’est pas le choix pour l’affichage dans n’importe quelle culture. L’une des rares raisons d'utiliser StringComparison.InvariantCulture pour la comparaison est de permettre de conserver les données ordonnées pour un affichage identique à travers les cultures. Par exemple, si un fichier de données volumineux qui contient une liste d’identificateurs triés pour l’affichage accompagne une application, l’ajout à cette liste nécessite une insertion avec un tri de style indifférent.
Choix d’un membre StringComparison pour votre appel de méthode
Le tableau suivant présente le mappage du contexte de chaîne sémantique à un membre d’énumération StringComparison :
Données | Comportement | Valeur System.StringComparison valeur |
---|---|---|
Identificateurs internes respectant la casse. Identificateurs respectant la casse dans des normes telles que XML et HTTP. Paramètres liés à la sécurité respectant la casse. |
Identificateur non linguistique, où correspondent exactement les octets. | Ordinal |
Identificateurs internes ne respectant pas la casse. Identificateurs ne respectant pas la casse dans des normes telles que XML et HTTP. Chemins d’accès aux fichiers. Clés et valeurs de Registre. Variables d'environnement. Identificateurs de ressource (par exemple, gérer les noms). Paramètres liés à la sécurité ne respectant pas la casse. |
Identificateur non linguistique, où le cas n’est pas pertinent. | OrdinalIgnoreCase |
Certaines données persistantes, linguistiquesment pertinentes. Affichage de données linguistiques nécessitant un ordre de tri fixe. |
Données indépendantes culturelles qui sont toujours pertinentes sur le plan linguistique. | InvariantCulture - ou - InvariantCultureIgnoreCase |
Données affichées à l’utilisateur. La plupart des entrées des utilisateurs. |
Données nécessitant des coutumes linguistiques locales. | CurrentCulture - ou - CurrentCultureIgnoreCase |
Méthodes de comparaison de chaînes courantes dans .NET
Les sections suivantes décrivent les méthodes les plus couramment utilisées pour la comparaison de chaînes.
String.Compare
Interprétation par défaut : StringComparison.CurrentCulture.
En tant qu'opération la plus centrale de l'interprétation de chaînes, toutes les instances de ces appels de méthode doivent être examinées pour déterminer si les chaînes doivent être interprétées d'après la culture actuelle ou être dissociées de la culture (symboliquement). L'opération appropriée est, en général, la dernière, et une comparaison StringComparison.Ordinal doit être utilisée à la place.
La classe System.Globalization.CompareInfo , retournée par la propriété CultureInfo.CompareInfo , inclut également une méthode Compare qui fournit un grand nombre d'options de correspondance (ordinale, ignorance des espaces blancs, ignorance du type Kana, etc.) au moyen de l'énumération d'indicateur CompareOptions .
String.CompareTo
Interprétation par défaut : StringComparison.CurrentCulture.
Cette méthode n'offre actuellement pas de surcharge spécifiant un type StringComparison . Il est généralement possible de convertir cette méthode en formulaire recommandé String.Compare(String, String, StringComparison) .
Types qui implémentent les interfaces IComparable et IComparable<T> implémentent cette méthode. Étant donné qu’il n’offre pas l’option d’un StringComparison paramètre, l’implémentation de types permet souvent à l’utilisateur de spécifier un StringComparer dans son constructeur. L’exemple suivant définit une FileName
classe dont le constructeur de classe inclut un StringComparer paramètre. Cet StringComparer objet est ensuite utilisé dans la FileName.CompareTo
méthode.
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
Interprétation par défaut : StringComparison.Ordinal.
La String classe vous permet de tester l’égalité en appelant les surcharges de méthode statique ou d’instance Equals , ou en utilisant l’opérateur d’égalité statique. Par défaut, les surcharges et l'opérateur utilisent la comparaison ordinale. Toutefois, nous vous recommandons quand même d'appeler une surcharge qui spécifie explicitement le type StringComparison , même si vous voulez effectuer une comparaison ordinale ; cela le simplifie la recherche d'une interprétation de chaîne particulière dans du code.
String.ToUpper et String.ToLower
Interprétation par défaut : StringComparison.CurrentCulture.
Soyez prudent lorsque vous utilisez les méthodes String.ToUpper() et String.ToLower(), car le fait de forcer une chaîne à prendre la forme d'une majuscule ou d'une minuscule est souvent utilisé comme une petite normalisation pour comparer des chaînes sans tenir compte de la casse. Si tel est le cas, vous devez envisager d'utiliser une comparaison ne respectant pas la casse.
Les méthodes String.ToUpperInvariant et String.ToLowerInvariant sont également disponibles. ToUpperInvariant est le moyen standard de normaliser la casse. Les comparaisons faites à l'aide de StringComparison.OrdinalIgnoreCase sont, sur le plan comportemental, la composition de deux appels : appel à ToUpperInvariant sur les deux arguments de chaîne, et exécution d'une comparaison à l'aide de StringComparison.Ordinal.
Des surcharges sont également disponibles pour la conversion en majuscules et en minuscules dans une culture spécifique, en passant à la méthode un objet CultureInfo qui représente cette culture.
Char.ToUpper et Char.ToLower
Interprétation par défaut : StringComparison.CurrentCulture.
Les méthodes Char.ToUpper(Char) et Char.ToLower(Char) fonctionnent de la même façon que les méthodes String.ToUpper() et String.ToLower() décrites dans la section précédente.
String.StartsWith et String.EndsWith
Interprétation par défaut : StringComparison.CurrentCulture.
Par défaut, ces deux méthodes effectuent une comparaison sensible à la culture. En particulier, ils peuvent ignorer les caractères non imprimables.
String.IndexOf et String.LastIndexOf
Interprétation par défaut : StringComparison.CurrentCulture.
Il existe un manque de cohérence dans la façon dont les surcharges par défaut de ces méthodes effectuent des comparaisons. Toutes les méthodes String.IndexOf et String.LastIndexOf qui incluent un paramètre Char effectuent une comparaison ordinale, mais les méthodes String.IndexOf et String.LastIndexOf par défaut qui incluent un paramètre String effectuent une comparaison sensible à la culture.
Si vous appelez la méthode String.IndexOf(String) ou String.LastIndexOf(String) et que vous lui passez une chaîne à localiser dans l'instance actuelle, nous vous recommandons d'appeler une surcharge qui spécifie explicitement le type StringComparison . Les surcharges qui incluent un Char argument ne vous permettent pas de spécifier un StringComparison type.
Méthodes qui effectuent indirectement une comparaison de chaînes
Certaines méthodes non-chaînes qui ont une comparaison de chaînes en tant qu’opération centrale utilisent le StringComparer type. La StringComparer classe inclut six propriétés statiques qui retournent StringComparer des instances dont StringComparer.Compare les méthodes effectuent les types de comparaisons de chaînes suivants :
- Comparaisons de chaînes dépendantes de la culture à l'aide de la culture actuelle. Cet StringComparer objet est retourné par la StringComparer.CurrentCulture propriété.
- Comparaisons ne respectant pas la casse à l'aide de la culture actuelle. Cet StringComparer objet est retourné par la StringComparer.CurrentCultureIgnoreCase propriété.
- Comparaisons non sensibles à la culture à l’aide des règles de comparaison de mots de la culture invariante. Cet StringComparer objet est retourné par la StringComparer.InvariantCulture propriété.
- Comparaisons ne respectant pas la casse et indépendantes de la culture à l'aide des règles de comparaison des mots de la culture dite indifférente. Cet StringComparer objet est retourné par la StringComparer.InvariantCultureIgnoreCase propriété.
- Comparaison ordinale. Cet StringComparer objet est retourné par la StringComparer.Ordinal propriété.
- Comparaison ordinale ne respectant pas la casse. Cet StringComparer objet est retourné par la StringComparer.OrdinalIgnoreCase propriété.
Array.Sort et Array.BinarySearch
Interprétation par défaut : StringComparison.CurrentCulture.
Lorsque vous stockez des données dans une collection ou que vous lisez des données persistantes d’un fichier ou d’une base de données dans une collection, le basculement de la culture actuelle peut invalider les invariants dans la collection. La Array.BinarySearch méthode suppose que les éléments du tableau à rechercher sont déjà triés. Pour trier n’importe quel élément de chaîne dans le tableau, la Array.Sort méthode appelle la String.Compare méthode pour classer des éléments individuels. L’utilisation d’un comparateur sensible à la culture peut être dangereuse si la culture change entre le moment où le tableau est trié et son contenu est recherché. Par exemple, dans le code suivant, le stockage et la récupération fonctionnent sur le comparateur fourni implicitement par la Thread.CurrentThread.CurrentCulture
propriété. Si la culture peut changer entre les appels à StoreNames
et DoesNameExist
, et surtout si le contenu du tableau est conservé à un moment donné entre les deux appels de méthode, la recherche binaire peut échouer.
// 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
Une variante recommandée apparaît dans l’exemple suivant, qui utilise la même méthode de comparaison ordinale (non sensible à la culture) à la fois pour trier et rechercher dans le tableau. Le code de modification est reflété dans les lignes étiquetées Line A
et Line B
dans les deux exemples.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function
Si ces données sont conservées et déplacées entre les cultures, et que le tri est utilisé pour présenter ces données à l’utilisateur, vous pouvez envisager d’utiliser StringComparison.InvariantCulture, ce qui fonctionne linguistiquement pour une meilleure sortie utilisateur, mais n’est pas affecté par les modifications de la culture. L’exemple suivant modifie les deux exemples précédents pour utiliser la culture invariante pour le tri et la recherche dans le tableau.
// 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
Exemple de collection : Constructeur de table de hachage
Les chaînes de hachage fournissent un deuxième exemple d’opération affectée par la façon dont les chaînes sont comparées.
L’exemple suivant instancie un objet Hashtable en lui passant l'objet StringComparer qui est retourné par la propriété StringComparer.OrdinalIgnoreCase. Étant donné qu'une classe StringComparer, dérivée de StringComparer, implémente l'interface IEqualityComparer, sa méthode GetHashCode est utilisée pour calculer le code de hachage des chaînes dans la table de hachage.
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