Pour afficher l’article en anglais, activez la case d’option Anglais. Vous pouvez aussi afficher la version anglaise dans une fenêtre contextuelle en faisant glisser le pointeur de la souris sur le texte.
Traduction
Anglais

Meilleures pratiques pour l'utilisation de chaînes dans le .NET Framework

 

Le .NET Framework offre une prise en charge complète 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 la même manière dans toutes les cultures. Quand des données de type chaîne culturellement indépendantes, telles que des balises XML, des balises HTML, des noms d'utilisateurs, des chemins d'accès aux fichiers et des noms d'objets système, sont interprétées comme si elles étaient dépendantes de la culture, le code d'application peut faire l'objet de bogues subtils, de performances médiocres et, dans certains cas, de problèmes de sécurité.

Cette rubrique examine les méthodes de tri, de comparaison et d'application de la casse dans .NET Framework, présente les recommandations pour sélectionner une méthode appropriée de gestion des chaînes et fournit des informations supplémentaires concernant les méthodes de gestion des chaînes. Elle décrit également comment les données mises en forme, telles que les données numériques et les données de date et d'heure, sont traitées pour l'affichage et pour le stockage.

Cette rubrique contient les sections suivantes :

Dans le cadre du développement à l'aide du .NET Framework, suivez les recommandations simples ci-après quand vous utilisez des chaînes :

Quand vous utilisez des chaînes, évitez les pratiques suivantes :

  • 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înes basées sur StringComparison.InvariantCulture dans la plupart des cas. L'une des rares exceptions est quand vous rendez persistantes des données linguistiquement explicites, mais dont la culture n'est pas spécifiée.

  • N'utilisez pas une surcharge de la méthode String.Compare ou CompareTo, et testez une valeur de retour de zéro pour déterminer si deux chaînes sont égales.

  • N'utilisez pas la mise en forme qui tient compte de la culture pour rendre des données numériques ou les données de date et d'heure sous forme de chaîne.

Retour au début

La plupart des méthodes de manipulation de chaînes dans le .NET Framework sont surchargées. Une ou plusieurs surcharges acceptent généralement des paramètres par défaut, contrairement à d'autres qui définissent avec précision la façon dont les chaînes doivent être comparées ou manipulées. La plupart des méthodes qui ne s'appuient pas sur des valeurs par défaut incluent un paramètre de type StringComparison, qui est une énumération spécifiant explicitement des règles pour la comparaison de chaînes selon la culture et la casse. Le tableau suivant décrit les membres de l'énumération StringComparison.

Membre StringComparison

Description

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 méthode IndexOf, qui retourne l'index d'une sous-chaîne contenue dans un objet String correspondant à un caractère ou à une chaîne, a neuf surcharges :

Nous vous recommandons de sélectionner une surcharge qui n'utilise pas de 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 mémoriser quelle méthode utilise quelle valeur par défaut, et les surcharges peuvent être facilement confondues.

  • L'objectif du code qui s'appuie sur des valeurs par défaut pour les appels de méthode n'est pas clair. 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 protocol et "http" peut entraîner le retour de false par le test d’égalité.

    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

En général, nous vous recommandons d'appeler une méthode qui ne s'appuie pas sur des valeurs par défaut, car elle rend l'objectif du code non équivoque. Cela rend ensuite le code plus lisible et plus facile à déboguer et à gérer. L'exemple suivant aborde les questions soulevées à propos de l'exemple précédent. Il explique que la comparaison ordinale est utilisée et que les différences de casse sont ignorées.

string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}

Retour au début

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 "my" s'affiche avant "string" dans une liste triée de chaînes, "my" doit être considéré comme inférieur ou égal à "string". En outre, la comparaison définit implicitement l'égalité. L'opération de comparaison retourne zéro pour les chaînes qu'il estime égales. Considérer qu'aucune chaîne n'est inférieure à l'autre constitue une bonne interprétation. La plupart des opérations significatives impliquant des chaînes incluent l'une des procédures suivantes, ou les deux : comparaison avec une autre chaîne et exécution d'une opération de tri bien définie.

Toutefois, l'évaluation de l'égalité ou de l'ordre de tri de deux chaînes ne produit pas un résultat correct unique ; 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 dite indifférente (culture aux paramètres régionaux non spécifiés basée sur la langue anglaise) peuvent produire des résultats différents.

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, sa valeur par défaut est le paramètre défini dans la fenêtre Options régionales du Panneau de configuration. Vous devez toujours utiliser des comparaisons basées sur la culture actuelle quand les données sont linguistiquement pertinentes et quand elles reflètent l'intervention de l'utilisateur dépendante de la culture.

Toutefois, le comportement de la comparaison et de la casse dans le .NET Framework change en fonction de la culture. Cela se produit quand une application s'exécute sur un ordinateur dont la culture est différente de celle de l'ordinateur sur lequel l'application a été développée, ou quand le thread en cours d'exécution change sa culture. Ce comportement est intentionnel, mais il reste peu évident à de nombreux développeurs. L'exemple suivant illustre les différences au niveau de l'ordre de tri entre les cultures Anglais (États-Unis) ("en-US") et Suédois ("sv-SE"). Notez que les mots "ångström", "Windows" et "Visual Studio" s'affichent à des positions différentes dans les tableaux de chaînes triées.

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

public class Example
{
   public static void Main()
   {
      string[] values= { "able", "ångström", "apple", "Æble", 
                         "Windows", "Visual Studio" };
      Array.Sort(values);
      DisplayArray(values);

      // Change culture to Swedish (Sweden).
      string originalCulture = CultureInfo.CurrentCulture.Name;
      Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
      Array.Sort(values);
      DisplayArray(values);

      // Restore the original culture.
      Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
    }

    private static void DisplayArray(string[] values)
    {
      Console.WriteLine("Sorting using the {0} culture:",  
                        CultureInfo.CurrentCulture.Name);
      foreach (string value in values)
         Console.WriteLine("   {0}", value);

      Console.WriteLine();
    }
}
// The example displays the following output:
//       Sorting using the en-US culture:
//          able
//          Æble
//          ångström
//          apple
//          Visual Studio
//          Windows
//       
//       Sorting using the sv-SE culture:
//          able
//          Æble
//          apple
//          Windows
//          Visual Studio
//          ångström

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 la culture actuelle sont la valeur par défaut pour les méthodes suivantes :

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.

Des bogues, plus ou moins subtils, peuvent émerger quand des données de type chaîne non linguistiques sont interprétées linguistiquement, ou quand des données de type 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 du caractère I en turc.

Dans presque tous les alphabets latins, y compris l'anglais (États-Unis), le caractère "i" (\u0069) est la version en 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") inclut un caractère "I avec un point", "İ" (\u0130), qui est la version en majuscule de "i". Le turc comprend également un caractère "i sans point" minuscule, "ı" (\u0131), dont la majuscule est "I". Ce comportement se produit également dans la culture azérie ("az").

Par conséquent, les suppositions faites sur la mise en majuscule 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 varieront suivant 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 "file" et "FILE".

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

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

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

Cette comparaison peut provoquer de sérieux problèmes si la culture est utilisée par inadvertance dans des paramètres de sécurité sensibles, comme dans l'exemple suivant. Un appel de méthode comme IsFileURI("file:") retourne true si la culture actuelle est Anglais (États-Unis), mais false si la culture actuelle est Turc. 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) 
{
   return path.StartsWith("FILE:", true, null);
}

Dans ce cas, étant donné que "file:" doit être interprété comme un identificateur non linguistique indépendant de la culture, le code doit plutôt être écrit comme indiqué dans l'exemple suivant.

public static bool IsFileURI(string path) 
{
   return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}

La spécification de la valeur StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase dans un appel de méthode correspond à une comparaison non linguistique dans laquelle les fonctionnalités de langages naturels 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 correspond le mieux à l'interprétation de chaînes prévue 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 fondamentalement d’un appel à la fonction strcmp du runtime 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.

Dans le .NET Framework, les chaînes peuvent contenir des caractères Null incorporés. L'une des différences les plus évidentes entre la comparaison ordinale et la comparaison dépendante de la culture (y compris les comparaisons qui utilisent la culture dite indifférente) concerne la gestion des caractères Null incorporés dans une chaîne. Ces caractères sont ignorés quand vous utilisez les méthodes String.Compare et String.Equals pour effectuer des comparaisons dépendantes de la culture (notamment des comparaisons qui utilisent la culture dite indifférente). Par conséquent, dans les comparaisons dépendantes de la culture, les chaînes qui contiennent des caractères Null incorporés peuvent être considérées comme égales à des chaînes qui n'en contiennent pas.

System_CAPS_importantImportant

Les méthodes de comparaison de chaînes ignorent les caractères Null incorporés, contrairement aux méthodes de recherche de chaînes telles que String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf et String.StartsWith.

L'exemple suivant effectue une comparaison dépendante de la culture de la chaîne "Aa" avec une chaîne semblable 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.

using System;

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

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

   private static string ShowBytes(string str)
   {
      string hexString = String.Empty;
      for (int ctr = 0; ctr < str.Length; ctr++)
      {
         string result = String.Empty;
         result = Convert.ToInt32(str[ctr]).ToString("X4");
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
         hexString += result;
      }
      return hexString.Trim();
   }
}
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Current Culture: 0
//          Invariant Culture: 0
//       With String.Equals:
//          Current Culture: True
//          Invariant Culture: True

Toutefois, les chaînes ne sont pas considérées comme égales quand vous utilisez la comparaison ordinale, comme le montre l'exemple suivant.

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

Console.WriteLine("   With String.Equals:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False

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 de la plage [A, Z] (\u0041-\u005A) correspond au caractère correspondant de la plage [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);

est équivalente à la comparaison suivante (mais plus rapide) :

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

Ces comparaisons restent très rapides.

System_CAPS_noteRemarque

StringComparison.OrdinalIgnoreCase constitue la meilleure représentation du comportement de chaîne du système de fichiers, des clés de Registre et des valeurs, ainsi que des variables d'environnement.

StringComparison.Ordinal et StringComparison.OrdinalIgnoreCase utilisent tous les deux directement les valeurs binaires et sont les plus adaptés à la mise en correspondance. Quand vous n'êtes pas sûr de vos paramètres de comparaison, utilisez l'une de ces deux valeurs. Toutefois, étant donné qu'elles effectuent une comparaison octet par octet, elles n'effectuent pas le tri selon un ordre de tri linguistique (comme un dictionnaire français), mais selon 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.

Les comparaisons avec la culture dite indifférente utilisent la propriété CompareInfo retournée par la propriété CultureInfo.InvariantCulture statique. 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 suppose être des caractères invariants équivalents. Cette stratégie peut être utile pour la gestion d'un jeu de comportements de chaîne dans toutes les cultures, mais elle donne 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 casse entre ces caractères traduits sont ignorées.

Les comparaisons qui utilisent StringComparison.InvariantCulture et StringComparison.Ordinal fonctionnent de la même manière sur les 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 jeu d'octets. L’objet CultureInfo.InvariantCulture.CompareInfo entraîne l’interprétation par la méthode Compare de certains jeux de caractères comme étant équivalents. Par exemple, l'équivalence suivante est valide dans la culture dite indifférente :

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 le caractère LETTRE MINUSCULE LATINE A AVEC DIACRITIQUE ROND EN CHEF "å" (\u00e5). Comme le montre l'exemple suivant, ce comportement diffère de la comparaison ordinale.

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

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                  separated, combined, 
                  String.Compare(separated, combined, 
                                 StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                  separated, combined,
                  String.Compare(separated, combined, 
                                 StringComparison.Ordinal) == 0);
// The example displays the following output:
//    Equal sort weight of a° and å using InvariantCulture: True
//    Equal sort weight of a° and å using Ordinal: False      

Lors de l'interprétation de noms de fichiers, de cookies ou de tout autre élément dans lequel peut s'afficher une combinaison telle que "å", les comparaisons ordinales offrent toujours le comportement le plus transparent et le plus approprié.

En définitive, la culture dite indifférente a très peu de propriétés qui la rendent utile pour la comparaison. Elle effectue la comparaison d'une manière linguistiquement pertinente, qui l'empêche de garantir une équivalence symbolique complète, mais elle ne constitue pas le choix approprié pour l'affichage dans n'importe quelle culture. L'une des rares raisons justifiant l'utilisation de StringComparison.InvariantCulture pour la comparaison est de rendre persistantes des données classées pour un affichage identique dans toutes les cultures. Par exemple, si un fichier de données volumineux qui contient une liste d'identificateurs triés à des fins d'affichage accompagne une application, un ajout à cette liste nécessiterait une insertion avec un tri de style invariant.

Retour au début

Le tableau suivant décrit le mappage de 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ù les octets correspondent exactement.

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, noms de handles).

Paramètres liés à la sécurité ne respectant pas la casse.

Identificateur non linguistique, où la casse n'est pas pertinente ; en particulier, les données stockées dans la plupart des services système Windows.

OrdinalIgnoreCase 

Certaines données rendues persistantes et linguistiquement pertinentes.

Affichage de données linguistiques qui nécessitent un ordre de tri fixe.

Données dont la culture n'est pas spécifiée qui sont toutefois linguistiquement pertinentes.

InvariantCulture  

ou

InvariantCultureIgnoreCase 

Données affichées à l'utilisateur.

La plupart des entrées d'utilisateur.

Données qui nécessitent des usages linguistiques locaux.

CurrentCulture

ou

CurrentCultureIgnoreCase 

Retour au début

Les sections suivantes décrivent les méthodes le plus souvent utilisées pour la comparaison de chaînes.

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.

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 dans la forme String.Compare(String, String, StringComparison) recommandée.

Les types qui implémentent les interfaces IComparable et IComparable<T> implémentent cette méthode. Étant donné qu'elle n'offre pas la possibilité d'un paramètre StringComparison, les types d'implémentation permettent souvent à l'utilisateur de spécifier un StringComparer dans leur constructeur. L'exemple suivant définit une classe FileName dont le constructeur de classe inclut un paramètre StringComparer. Cet objet StringComparer est ensuite utilisé dans la méthode FileName.CompareTo.

using System;

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

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

      this.fname = name;

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

   public string Name
   {
      get { return fname; }
   }

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

      if (! (obj is FileName))
         return comparer.Compare(this.fname, obj.ToString());
      else
         return comparer.Compare(this.fname, ((FileName) obj).Name);
   }
}

Interprétation par défaut : StringComparison.Ordinal.

La classe String vous permet de tester l'égalité en appelant les surcharges de méthode Equals statique ou d'instance, 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.

Interprétation par défaut : StringComparison.CurrentCulture.

Vous devez faire preuve de prudence quand vous utilisez ces méthodes, car imposer une majuscule ou une minuscule dans une chaîne est souvent utilisé comme une petite normalisation pour la comparaison de chaînes indépendamment 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.

Interprétation par défaut : StringComparison.CurrentCulture.

Ces méthodes fonctionnent de la même façon que les méthodes String.ToUpper et String.ToLower décrites dans la section précédente.

Interprétation par défaut : StringComparison.CurrentCulture.

Par défaut, ces deux méthodes effectuent une comparaison dépendante de la culture.

Interprétation par défaut : StringComparison.CurrentCulture.

La façon dont les surcharges par défaut de ces méthodes effectuent les comparaisons n'est pas cohérente. 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 dépendante de 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 argument Char ne vous permettent pas de spécifier un type StringComparison.

Retour au début

Certaines méthodes autres que les méthodes de chaîne dont l'opération centrale est la comparaison de chaînes utilisent le type StringComparer. La classe StringComparer inclut six propriétés statiques qui retournent des instances de StringComparer dont les méthodes StringComparer.Compare effectuent les types de comparaisons de chaînes suivants :

Interprétation par défaut : StringComparison.CurrentCulture.

Quand vous stockez des données dans une collection ou quand vous lisez des données persistantes à partir d'un fichier ou d'une base de données dans une collection, le changement de culture actuelle peut invalider les invariants de la collection. La méthode Array.BinarySearch suppose que les éléments du tableau dans lequel effectuer la recherche sont déjà triés. Pour trier tout élément de chaîne dans le tableau, la méthode Array.Sort appelle la méthode String.Compare pour classer les éléments individuels. L'utilisation d'un comparateur dépendant de la culture peut s'avérer dangereux si la culture change entre le moment où le tableau est trié et le moment où son contenu fait l'objet d'une recherche. Par exemple, dans le code suivant, le stockage et la récupération fonctionnent sur le comparateur fourni implicitement par la propriété Thread.CurrentThread.CurrentCulture. Si la culture peut changer entre les appels à StoreNames et DoesNameExist, et surtout si le contenu du tableau est rendu persistant à un moment donné entre les deux appels de méthode, la recherche binaire peut échouer.

// Incorrect.
string []storedNames;

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

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

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

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

L'exemple suivant, qui utilise la même méthode de comparaison ordinale (indépendante de la culture) pour trier le tableau et pour effectuer une recherche, présente une variation recommandée. Le code de modification est reflété dans les lignes libellées Line A et Line B dans les deux exemples.

// Correct.
string []storedNames;

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

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

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

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

Si ces données sont rendues persistantes et déplacées dans toutes les cultures, et que le tri est utilisé pour présenter ces données à l'utilisateur, vous pouvez envisager d'utiliser StringComparison.InvariantCulture, qui fonctionne linguistiquement pour une meilleure sortie d'utilisateur mais n'est pas affecté par les modifications apportées à la culture. L'exemple suivant modifie les deux exemples précédents pour utiliser la culture dite indifférente pour le tri du tableau et la recherche dans celui-ci.

// Correct.
string []storedNames;

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

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

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

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

Le hachage de chaînes constitue un deuxième exemple d'opération qui est affectée par la façon dont des 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 de chaînes dans la table de hachage.

const int initialTableCapacity = 100;
Hashtable h;

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

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

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

Retour au début

Lorsque vous affichez des données non-chaînées telles que les nombres et les dates et heures aux utilisateurs, mettez-les en forme en utilisant les paramètres de la culture de l'utilisateur. Par défaut, la méthode String.Format et les méthodes ToString des types numériques et des types de date et d’heure, utilisent la culture du thread actuelle pour les opérations de mise en forme. Pour spécifier explicitement que la méthode de mise en forme doit utiliser la culture actuelle, vous pouvez appeler une surcharge d'une méthode de mise en forme ayant un paramètre provider, comme String.Format(IFormatProvider, String, Object[]) ou DateTime.ToString(IFormatProvider), et lui passer la propriété CultureInfo.CurrentCulture.

Vous pouvez rendre persistantes des données non-chaînées soit comme données binaires, soit comme données mises en forme. Si vous choisissez de l'enregistrer en tant que données mises en forme, vous devez appeler une surcharge de méthode de mise en forme qui inclut un paramètre provider et le passer à la propriété CultureInfo.InvariantCulture. La culture dite indifférente fournit un format cohérent pour les données mises en forme qui est indépendant de la culture et de l'ordinateur. En revanche, assurer la persistance de données mises en forme à l'aide de cultures autres que la culture dite indifférente a plusieurs limites :

  • Les données seront vraisemblablement inutilisables si elles sont récupérées sur un système ayant une autre culture, ou si l'utilisateur du système actuel change la culture actuelle et essaie de récupérer les données.

  • Les propriétés d'une culture sur un ordinateur spécifique peuvent différer des valeurs standard. À tout moment, un utilisateur peut personnaliser les paramètres d'affichage selon la culture. De ce fait, les données mises en forme sont stockées sur un système et peuvent ne pas être lisibles lorsque l'utilisateur personnalise les paramètres de culture. La portabilité des données mises en forme sur différents ordinateurs peut être encore plus limitée.

  • Des normes internationales, régionales ou nationales qui régissent la mise en forme des nombres ou des dates et heures évoluent au fil du temps, et ces modifications sont intégrées dans les mises à jour du système d'exploitation Windows. Quand les conventions de mise en forme changent, les données qui ont été mises en forme en utilisant les conventions antérieures peuvent devenir illisibles.

L'exemple suivant illustre la portabilité limitée qui résulte de l'utilisation de la mise en forme qui tient compte de la culture pour assurer la persistance des données. L'exemple enregistre un tableau de valeurs de date et d'heure dans un fichier. Celles-ci sont mises en forme en utilisant les conventions culturelles de l'anglais (États-Unis). Une fois que l'application change la culture du thread actuel pour appliquer le français (Suisse), elle essaie de lire les valeurs enregistrées en utilisant les conventions de mise en forme de la culture actuelle. La tentative de lecture des éléments de données par deux fois renvoie une exception FormatException, et le tableau de dates contient maintenant deux éléments incorrects qui sont identiques à MinValue.

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example
{
   private static string filename = @".\dates.dat";

   public static void Main()
   {
      DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0), 
                           new DateTime(1818, 5, 5, 7, 19, 0), 
                           new DateTime(1870, 4, 22, 23, 54, 0),  
                           new DateTime(1890, 9, 8, 6, 47, 0), 
                           new DateTime(1905, 2, 18, 15, 12, 0) }; 
      // Write the data to a file using the current culture.
      WriteData(dates);
      // Change the current culture.
      Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
      // Read the data using the current culture.
      DateTime[] newDates = ReadData();
      foreach (var newDate in newDates)
         Console.WriteLine(newDate.ToString("g"));
   }

   private static void WriteData(DateTime[] dates) 
   {
      StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);    
      for (int ctr = 0; ctr < dates.Length; ctr++) {
         sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
         if (ctr < dates.Length - 1) sw.Write("|");   
      }      
      sw.Close();
   }

   private static DateTime[] ReadData() 
   {
      bool exceptionOccurred = false;

      // Read file contents as a single string, then split it.
      StreamReader sr = new StreamReader(filename, Encoding.UTF8);
      string output = sr.ReadToEnd();
      sr.Close();   

      string[] values = output.Split( new char[] { '|' } );
      DateTime[] newDates = new DateTime[values.Length]; 
      for (int ctr = 0; ctr < values.Length; ctr++) {
         try {
            newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
         }
         catch (FormatException) {
            Console.WriteLine("Failed to parse {0}", values[ctr]);
            exceptionOccurred = true;
         }
      }      
      if (exceptionOccurred) Console.WriteLine();
      return newDates;
   }
}
// The example displays the following output:
//       Failed to parse 4/22/1870 11:54 PM
//       Failed to parse 2/18/1905 3:12 PM
//       
//       05.06.1758 21:26
//       05.05.1818 07:19
//       01.01.0001 00:00
//       09.08.1890 06:47
//       01.01.0001 00:00
//       01.01.0001 00:00

Toutefois, si vous remplacez la propriété CultureInfo.CurrentCulture par CultureInfo.InvariantCulture dans les appels à DateTime.ToString(String, IFormatProvider) et DateTime.Parse(String, IFormatProvider), les données de date et d'heure persistantes sont correctement restaurées, comme le montre la sortie suivante.


06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12

Afficher: