
Ouvrez n’importe quelle base de code C# : il y a de fortes chances que vous y croisiez un texte1.ToLower() == texte2.ToLower(). C’est le réflexe universel pour comparer deux chaînes de caractères en ignorant la casse. Pourtant, derrière cette syntaxe en apparence inoffensive se cachent un véritable gouffre pour vos performances (via des allocations mémoire fantômes) et un terrain miné pour des bugs culturels silencieux. Dans cet article, nous allons voir comment arrêter de maltraiter votre Garbage Collector.
C’est un extrait de code que l’on retrouve dans presque toutes les codebases .NET du monde. Pour vérifier si deux chaînes de caractères sont identiques sans tenir compte de la casse (majuscules/minuscules), le réflexe est souvent d’écrire ceci :
if (emailInput.ToLower() == user.Email.ToLower())
{
// Connexion autorisée...
}
Cela semble inoffensif, lisible et ça fonctionne. Pourtant, dans une application en production, c’est une mauvaise pratique. Utiliser .ToLower() ou .ToUpper()) pour effectuer des comparaisons pose deux problèmes majeurs : un problème de performance et un problème de fiabilité.
Il est donc important de changer drastiquement ses pratiques opérationnelles pour résoudre ce problème.
les allocations inutiles
En C#, le type string est immuable. Cela signifie qu’une fois créée, une chaîne de caractères ne peut plus être modifiée en mémoire.
Lorsque vous appelez la méthode .ToLower(), le framework .NET ne modifie pas la chaîne existante. Il va parcourir chaque caractère, les convertir en minuscules, et allouer une toute nouvelle chaîne sur le tas (Heap).
Dans l’exemple ci-dessus, nous créons deux nouvelles chaînes en mémoire juste pour effectuer un test conditionnel. Si cette comparaison se trouve dans une boucle foreach qui traite 100 000 éléments, vous venez d’allouer 200 000 objets inutiles.
Conséquence directe : vous augmentez drastiquement la pression sur le Garbage Collector (GC), ce qui va provoquer des micro-pauses et dégrader les performances de votre application.
la sensibilité à la Culture
La méthode .ToLower() n’est pas absolue : elle dépend de la culture (les paramètres régionaux) du Thread en cours d’exécution. C’est ce qu’on appelle le problème du « I turc » (The Turkish I problem).
Dans la plupart des alphabets occidentaux, la version minuscule de la lettre I (majuscule) est i (minuscule). Mais dans l’alphabet turc, la majuscule de i est İ (avec un point), et la minuscule de I (sans point) est ı (sans point).
Si votre code s’exécute sur un serveur dont la culture est définie sur le turc (tr-TR), la comparaison ToLower() de mots anglais ou de clés d’API pourrait renvoyer false de manière totalement inattendue. Ce genre de bug est un cauchemar à déboguer.
La solution : utiliser StringComparison
Heureusement pour nous, le framework .NET fournis des solutions natives pour résoudre ce genre de problématique. Elles sont optimisées et sans allocations mémoires :
La bonne façon de comparer deux chaînes en ignorant la casse est d’utiliser la méthode string.Equals() :
if (string.Equals(emailInput, user.Email, StringComparison.OrdinalIgnoreCase))
{
// Code exécuté proprement, sans allocation et sans bug de culture
}
Comment ça fonctionne exactement ?
Au lieu de créer de nouvelles chaînes, string.Equals parcourt les deux chaînes d’origine simultanément. Dès qu’elle détecte une différence (en ignorant mathématiquement la casse), elle s’arrête. Aucune nouvelle donnée n’est allouée sur le Heap.
Quel StringComparison choisir ?
OrdinalIgnoreCase(Le choix par défaut) : Compare la valeur binaire (les octets) des caractères. C’est la méthode la plus rapide et la plus sûre pour la logique interne (identifiants, clés de dictionnaires, noms de fichiers, JSON). Le contexte culturel est ignoré.Ordinal: Idem, mais en respectant la casse (sensible aux majuscules/minuscules). C’est la comparaison la plus rapide possible en .NET.CurrentCulture/CurrentCultureIgnoreCase: À utiliser uniquement pour des données qui vont être affichées ou triées pour l’utilisateur final. Cela respectera les règles linguistiques de la machine.InvariantCulture: Utilisé historiquement pour formater des données indépendantes de la culture, mais dans 99% des cas de comparaison de texte pur, Ordinal est aujourd’hui recommandé à la place.
Entity Framework Core : L’exception
Il y a un cas spécifique où l’utilisation de StringComparison.OrdinalIgnoreCase va faire crasher votre application : les requêtes LINQ to SQL via Entity Framework Core (EF Core).
// ❌ CELA VA PLANTER DANS EF CORE
var user = await dbContext.Users
.FirstOrDefaultAsync(u => string.Equals(u.Email, emailInput, StringComparison.OrdinalIgnoreCase));
Pourquoi ?
Parce qu’EF Core traduit votre code C# en langage SQL. Or, le moteur de traduction d’EF Core ne sait pas comment traduire l’énumération StringComparison en requête SQL. Il va lever une exception de type InvalidOperationException.
Conclusion : que faire ?
Dans le contexte d’EF Core, laissez simplement la comparaison normale (==) ou utilisez .ToLower(). Ce n’est pas un problème de performance ici, car ce code ne s’exécute pas en C#. Il est traduit en SQL (SELECT * FROM Users WHERE LOWER(Email) = …).
De plus, par défaut, la plupart des bases de données SQL Server sont configurées avec une Collation insensible à la casse (ex: SQL_Latin1_General_CP1_CI_AS où CI = Case Insensitive), ce qui rend même le .ToLower() inutile en SQL.


