Comment comparer des chaînes en C#

Vous comparez les chaînes pour répondre à l’une des deux questions suivantes : « Ces deux chaînes sont-elles égales ? » ou « Dans quel ordre ces chaînes doivent-elles être placées lors du tri ? »

Ces deux questions sont compliquées en raison des facteurs qui affectent les comparaisons de chaînes :

  • Vous pouvez choisir une comparaison ordinale ou linguistique.
  • Vous pouvez choisir si la casse a une incidence.
  • Vous pouvez choisir des comparaisons propres à la culture.
  • Les comparaisons linguistiques dépendent de la culture et de la plateforme.

Les champs d’énumération System.StringComparison représentent ces choix :

  • CurrentCulture : comparez les chaînes à l’aide de règles de tri dépendantes de la culture et de la culture actuelle.
  • CurrentCultureIgnoreCase : comparez les chaînes à l’aide de règles de tri dépendantes de la culture, de la culture actuelle, et sans tenir compte de la casse des chaînes comparées.
  • InvariantCulture : comparez les chaînes à l’aide de règles de tri dépendantes de la culture et de la culture invariante.
  • InvariantCultureIgnoreCase : comparez les chaînes à l’aide de règles de tri dépendantes de la culture, de la culture invariante, et sans tenir compte de la casse des chaînes comparées.
  • Ordinal : comparez les chaînes à l’aide de règles de tri ordinal (binaire).
  • OrdinalIgnoreCase : comparez les chaînes à l’aide de règles de tri ordinal (binaire) sans tenir compte de la casse des chaînes comparées.

Remarque

Les exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter. La code modifié s’exécute dans la fenêtre interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.

Quand vous comparez des chaînes, vous les ordonnez les unes par rapport aux autres. Les comparaisons permettent de trier une séquence de chaînes. Quand la séquence est dans un ordre connu, elle est plus facile à observer, que ce soit pour les logiciels ou pour les humains. Les autres comparaisons peuvent vérifier si les chaînes sont identiques. Ces vérifications de similitude se rapprochent de celles d’égalité, sauf que certaines différences, comme celles qui concernent la casse, peuvent être ignorées.

Comparaisons ordinales par défaut

Par défaut, les opérations les plus courantes :

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")}");

La comparaison ordinale par défaut ne prend pas en compte les règles linguistiques lors de la comparaison de chaînes. Elle compare la valeur binaire de chaque objet Char dans les deux chaînes. La comparaison ordinale par défaut respecte ainsi également la casse.

Le test d’égalité avec String.Equals et les opérateurs == et != diffère de la comparaison de chaîne à l’aide des méthodes String.CompareTo et Compare(String, String). Ils effectuent tous une comparaison respectant la casse. Toutefois, alors que les tests d’égalité effectuent une comparaison ordinale, les méthodes CompareTo et Compare effectuent une comparaison linguistique tenant compte de la culture à l’aide de la culture actuelle. Effacez l’intention de votre code en appelant une surcharge qui spécifie explicitement le type de comparaison à effectuer.

Comparaisons ordinales ne respectant pas la casse

La méthode String.Equals(String, StringComparison) vous permet de spécifier une valeur StringComparison de StringComparison.OrdinalIgnoreCase pour une comparaison ordinale ne respectant pas la casse. Il existe également une méthode String.Compare(String, String, StringComparison) statique qui effectue une comparaison ordinale ne respectant pas la casse si vous spécifiez la valeur StringComparison.OrdinalIgnoreCase pour l’argument StringComparison. Ces comparaisons sont affichées dans le code suivant :

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");

Ces méthodes utilisent les conventions de casse de la culture invariante lorsque vous effectuez une comparaison ordinale ne respectant pas la casse.

Comparaisons linguistiques

De nombreuses méthodes de comparaison de chaînes (par exemple String.StartsWith) utilisent par défaut des règles linguistiques liées à la culture active pour classer leurs entrées. Cette comparaison linguistique est parfois appelée « ordre de tri des mots ». Quand vous effectuez une comparaison linguistique, certains caractères Unicode non alphanumériques peuvent avoir une pondération spéciale. Par exemple, un poids faible peut être affecté à un trait d’union « - », de sorte que « co-op » et « coop » s’affichent l’un à côté de l’autre dans l’ordre de tri. Certains caractères de contrôle non imprimables peuvent être ignorés. Par ailleurs, certains caractères Unicode peuvent être équivalents à une séquence d’instances Char. L’exemple suivant utilise l’expression « Ils dansent dans la rue ». en allemand avec « ss » (U+0073 U+0073) dans une chaîne et « ß » (U+00DF) dans une autre. Du point de vue linguistique (dans Windows), « ss » équivaut à l’Esszet allemand : le caractère « ß » dans les cultures « en-US » et « 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");
}

Sur Windows, avant .NET 5 l’ordre de tri pour « cop », « coop » et « co-op » change quand vous passez d’une comparaison linguistique à une comparaison ordinale. De même, les deux phrases en allemand ne sont pas comparées de la même façon selon le type de comparaison. Avant .NET 5, les API de globalisation .NET utilisaient des bibliothèques NLS (National Language Support). Dans .NET 5 et les versions ultérieures, les API de globalisation .NET utilisent des bibliothèques de composants internationaux pour Unicode (ICU), qui unifient le comportement de globalisation .NET sur tous les systèmes d’exploitation pris en charge.

Comparaisons utilisant des cultures spécifiques

L’exemple suivant stocke des objets CultureInfo pour les cultures en-US et de-DE. Les comparaisons sont effectuées à l’aide de l’un objet CultureInfo pour garantir une comparaison propre à la culture. La culture utilisée affecte les comparaisons linguistiques. L’exemple suivant montre les résultats de la comparaison des deux phrases en allemand avec la culture « en-US » et la culture « 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");
}

Les comparaisons dépendantes de la culture sont généralement utilisées pour comparer et trier des chaînes entrées par plusieurs utilisateurs. Les caractères et les conventions de tri de ces chaînes peuvent varier selon les paramètres régionaux de l’ordinateur de l’utilisateur. Même les chaînes qui contiennent des caractères identiques peuvent être triées différemment selon la culture du thread actuel.

Tri linguistique et recherche de chaînes dans des tableaux

Les exemples suivants montrent comment trier et rechercher des chaînes dans un tableau à l’aide d’une comparaison linguistique dépendante de la culture actuelle. Vous utilisez les méthodes statiques Array qui prennent un paramètre System.StringComparer.

L’exemple suivant montre comment trier un tableau de chaînes à l’aide de la culture actuelle :

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}");
}

Une fois que le tableau est trié, vous pouvez rechercher des entrées à l’aide d’une recherche binaire. Une recherche binaire commence au milieu de la collection pour déterminer la moitié de la collection qui doit contenir la chaîne recherchée. Chaque comparaison ultérieure divise à son tour en deux la partie restante de la collection. Le tableau est trié à l’aide de StringComparer.CurrentCulture. La fonction locale ShowWhere affiche des informations sur l’emplacement de la chaîne. Si la chaîne est introuvable, la valeur retournée indique où elle serait si elle a été trouvée.

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}.");
    }
}

Tri ordinal et recherche dans des collections

Le code suivant utilise la classe de collection System.Collections.Generic.List<T> pour stocker les chaînes. Les chaînes sont triées à l’aide de la méthode List<T>.Sort. Cette méthode a besoin d’un délégué qui compare et trie les deux chaînes. La méthode String.CompareTo fournit cette fonction de comparaison. Exécutez l’exemple et observez l’ordre. Cette opération de tri utilise un tri ordinal respectant la casse. Vous devez utiliser les méthodes statiques String.Compare pour spécifier des règles de comparaison différentes.

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}");
}

Une fois triée, la liste de chaînes peut être parcourue à l’aide d’une recherche binaire. L’exemple suivant montre comment effectuer une recherche dans la liste triée à l’aide de la même fonction de comparaison. La fonction locale ShowWhere montre où se trouve le texte recherché ou là où il devrait se trouver :

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}.");
    }
}

Vérifiez toujours que vous utilisez le même type de comparaison pour le tri et la recherche. L’utilisation de différents types de comparaison pour le tri et la recherche produit des résultats inattendus.

Les classes de collection telles que System.Collections.Hashtable, System.Collections.Generic.Dictionary<TKey,TValue> et System.Collections.Generic.List<T> ont des constructeurs qui prennent un paramètre System.StringComparer quand le type des éléments ou des clés est string. En général, vous devez utiliser ces constructeurs chaque fois que cela vous est possible, et spécifier StringComparer.Ordinal ou StringComparer.OrdinalIgnoreCase.

Voir aussi