Menu

Dictionary en C# : Add, TryGetValue, parcours et tri

Dictionary<TKey, TValue> associe des clés à des valeurs avec une recherche rapide. Apprenez à ajouter et mettre à jour des entrées, lire sans risque avec TryGetValue, parcourir les KeyValuePair, compter des occurrences, ignorer la casse des clés, et garder les clés triées avec SortedDictionary.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Dictionary<TKey, TValue> stocke des valeurs sous des clés uniques et trouve une valeur par sa clé en un temps à peu près constant, quel que soit le nombre d'entrées. C'est l'équivalent C# d'une table de hachage : un annuaire du nom vers le numéro, un cache de l'identifiant vers l'enregistrement, un compteur par mot.

Créer un dictionnaire et lire des valeurs

Sortie :

12
2.50
2
True
False

Les deux formes d'initialiseur font la même chose. La forme ["key"] = value (C# 6) utilise l'indexeur, donc une clé répétée écrase ; la forme { key, value } appelle Add, donc une clé répétée lève une exception à l'exécution de la ligne.

ContainsKey est une recherche par hachage et elle est rapide. ContainsValue doit parcourir chaque entrée, car les valeurs ne sont pas indexées.

Add, l'indexeur ou TryAdd

Il existe trois façons d'insérer une entrée, et elles ne diffèrent que par ce qui se passe quand la clé existe déjà :

Sortie :

26
Caught ArgumentException
True
False
31

Qu'Add lève une exception sur un doublon est une fonctionnalité : cela fait apparaître des données qui auraient dû être uniques et ne l'étaient pas. Utilisez l'indexeur quand vous voulez dire « insérer ou mettre à jour », et TryAdd (.NET Core 2.0 et plus) quand la première valeur doit l'emporter.

Les clés ne peuvent pas valoir null. Add(null, ...) ou dict[null] lève ArgumentNullException. Les valeurs peuvent valoir null quand leur type le permet.

KeyNotFoundException et TryGetValue

Lire avec l'indexeur une clé absente lève KeyNotFoundException. C'est l'erreur de dictionnaire la plus courante, et la correction est presque toujours TryGetValue.

Sortie :

Caught KeyNotFoundException
Found ana@example.com
Missing, value is null: True
no email

TryGetValue fait une seule recherche par hachage et indique le succès par un bool. Le schéma if (dict.ContainsKey(k)) { var v = dict[k]; } fonctionne mais recherche la clé deux fois. Quand la clé est absente, la variable out reçoit la valeur par défaut du type (null, 0, false).

Sur .NET Core 2.0 et plus, il existe aussi GetValueOrDefault(key, fallback), qui renvoie la valeur de repli quand la clé est absente : emails.GetValueOrDefault(103, "no email").

Mettre à jour et supprimer des entrées

Sortie :

2
True
False
1
0

cart["milk"] += 1 lève KeyNotFoundException si milk n'est pas encore dans le dictionnaire, car l'opération lit avant d'écrire. Remove renvoie false au lieu de lever une exception pour une clé absente, donc inutile de vérifier ContainsKey d'abord.

Parcourir : KeyValuePair, Keys et Values

Un foreach sur un dictionnaire produit des éléments KeyValuePair<TKey, TValue>, chacun avec une Key et une Value.

Sortie :

Ana: 88
Ben: 72
Chloe: 95
Ana Ben Chloe 
Total 255
77

pair.Value est en lecture seule, donc mettre à jour des valeurs passe par l'indexeur. La dernière boucle parcourt une copie List<string> des clés, ce qui est toujours sûr ; parcourir directement scores.Keys en écrasant des valeurs existantes est autorisé sur .NET Core 3.0 et plus, mais lève InvalidOperationException sur .NET Framework.

Ajouter une nouvelle clé dans un foreach sur le même dictionnaire lève InvalidOperationException dans toutes les versions. Supprimer pendant l'énumération lève une exception sur .NET Framework et est autorisé depuis .NET Core 3.0. Le code qui doit fonctionner partout rassemble d'abord les clés à supprimer, puis les supprime après la boucle.

Avec C# 7 et .NET Core 2.0 ou plus, KeyValuePair peut être déconstruit dans l'en-tête de la boucle :

foreach (var (name, score) in scores)
{
    Console.WriteLine($"{name}: {score}");
}

Compter avec un dictionnaire

Compter des occurrences est l'usage classique. Lisez le compte actuel avec TryGetValue (une clé absente donne 0), puis réécrivez-le.

Sortie :

the 3
cat 1
and 2
dog 1
bird 1

La même forme sert à regrouper des éléments : Dictionary<string, List<Order>>, où vous récupérez la liste avec TryGetValue, en créez et stockez une si elle manque, puis appelez Add dessus. Pour compter ou regrouper ponctuellement, LINQ le fait en une expression : words.GroupBy(w => w).ToDictionary(g => g.Key, g => g.Count()). Voir LINQ.

Clés insensibles à la casse avec un comparateur

Les clés chaînes se comparent exactement par défaut : "Apple" et "apple" sont deux clés. Passez un IEqualityComparer<string> au constructeur pour changer cela.

Sortie :

False
text/html
1

StringComparer.OrdinalIgnoreCase est le bon choix pour les identifiants comme les en-têtes HTTP, les extensions de fichiers et les noms d'utilisateur. Appeler .ToLower() sur chaque clé avant de la stocker fonctionne aussi, mais il est facile de l'oublier à un endroit.

Pour des clés de votre propre classe, le dictionnaire appelle GetHashCode et Equals sur la clé. Une classe qui ne les redéfinit pas compare par référence, donc deux objets distincts aux mêmes champs sont des clés différentes. Voir HashSet pour écrire ce couple de méthodes.

Ordre, tri et SortedDictionary

Un Dictionary ne promet aucun ordre d'énumération. En pratique, un dictionnaire qui n'a reçu que des ajouts s'énumère dans l'ordre d'insertion, mais après un Remove, un Add ultérieur peut réutiliser l'emplacement libéré et apparaître plus tôt. Le code ne doit jamais en dépendre.

Quand vous avez besoin d'un ordre, triez au moment de l'utilisation ou utilisez une collection triée :

Sortie :

Cairo 210
Lima 340
Oslo 520
By value, highest first:
Oslo 520
Lima 340
Cairo 210
Berlin, Cairo, Lima, Oslo

SortedDictionary<TKey, TValue> garde ses clés triées en permanence (c'est un arbre équilibré), donc les recherches et les insertions sont en O(log n) au lieu de O(1). Utilisez-le quand vous énumérez souvent dans l'ordre des clés ; triez un dictionnaire normal avec LINQ quand vous n'avez besoin de l'ordre qu'une fois. SortedList<TKey, TValue> est une troisième option qui utilise moins de mémoire mais devient lente pour les insertions quand elle est grande.

Référence rapide

TâcheCode
Créernew Dictionary<string, int>()
Insérer ou écraserd[k] = v
Insérer, exception sur doublond.Add(k, v)
Insérer seulement si nouveaud.TryAdd(k, v)
Lire, exception si absentd[k]
Lire sans risqued.TryGetValue(k, out var v)
La clé existed.ContainsKey(k)
Supprimerd.Remove(k) (renvoie un bool)
Tailled.Count
Clés, valeursd.Keys, d.Values
Trié par cléd.OrderBy(p => p.Key) ou SortedDictionary
Ignorer la cassenew Dictionary<string, T>(StringComparer.OrdinalIgnoreCase)

Erreurs courantes

  • Lire une clé absente avec d[k]. Cela lève KeyNotFoundException ; utilisez TryGetValue.
  • Appeler Add pour une clé qui existe peut-être. Cela lève ArgumentException ; utilisez l'indexeur ou TryAdd.
  • Ajouter des clés dans un foreach sur le dictionnaire. Cela lève InvalidOperationException ; rassemblez les modifications et appliquez-les après.
  • Dépendre de l'ordre d'énumération. Triez, ou utilisez SortedDictionary.
  • Modifier les champs d'un objet clé après son insertion. Son code de hachage change et l'entrée devient introuvable.
  • ContainsKey puis l'indexeur. Deux recherches ; TryGetValue n'en fait qu'une.

Questions fréquentes

Quelle est la différence entre Dictionary.Add et l'indexeur en C# ?

dict.Add(key, value) insère une nouvelle entrée et lève ArgumentException si la clé existe déjà. dict[key] = value insère l'entrée si la clé est nouvelle et écrase la valeur si elle existe, sans jamais lever d'exception pour un doublon. TryAdd(key, value) n'insère que si la clé est nouvelle et renvoie false sinon.

Comment fonctionne TryGetValue en C# ?

dict.TryGetValue(key, out var value) renvoie true et remplit value quand la clé existe, et renvoie false en mettant value à la valeur par défaut de son type quand elle n'existe pas. Il fait une seule recherche, là où ContainsKey suivi de dict[key] en fait deux, et il ne lève jamais KeyNotFoundException.

Comment parcourir un Dictionary en C# ?

foreach (KeyValuePair<string, int> pair in dict) donne chaque entrée avec pair.Key et pair.Value. Pour ne parcourir que les clés ou que les valeurs, utilisez dict.Keys ou dict.Values. N'ajoutez pas de clés au dictionnaire dans cette boucle : cela lève InvalidOperationException.

Un Dictionary C# est-il ordonné ?

Aucun ordre n'est garanti. Un dictionnaire qui n'a jamais reçu que des ajouts s'énumère en général dans l'ordre d'insertion, mais après un Remove de nouvelles entrées peuvent occuper l'emplacement libéré, et l'ordre change. Triez quand vous avez besoin d'un ordre : dict.OrderBy(p => p.Key), ou utilisez SortedDictionary<TKey, TValue>, qui s'énumère toujours par clé.

Comment rendre les clés d'un Dictionary insensibles à la casse ?

Passez un comparateur au constructeur : new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase). Ensuite, "Apple" et "apple" sont la même clé pour les recherches, Add et ContainsKey. Le comparateur est fixé à la création du dictionnaire.

Qu'est-ce qu'un KeyValuePair en C# ?

KeyValuePair<TKey, TValue> est la struct qu'un dictionnaire vous donne pour chaque entrée quand vous l'énumérez. Elle a des propriétés Key et Value en lecture seule, donc vous ne pouvez pas modifier une entrée à travers elle ; écrivez plutôt dict[pair.Key] = newValue (après la boucle, ou sur une copie des clés).

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER