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âche | Code |
|---|---|
| Créer | new Dictionary<string, int>() |
| Insérer ou écraser | d[k] = v |
| Insérer, exception sur doublon | d.Add(k, v) |
| Insérer seulement si nouveau | d.TryAdd(k, v) |
| Lire, exception si absent | d[k] |
| Lire sans risque | d.TryGetValue(k, out var v) |
| La clé existe | d.ContainsKey(k) |
| Supprimer | d.Remove(k) (renvoie un bool) |
| Taille | d.Count |
| Clés, valeurs | d.Keys, d.Values |
| Trié par clé | d.OrderBy(p => p.Key) ou SortedDictionary |
| Ignorer la casse | new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase) |
Erreurs courantes
- Lire une clé absente avec
d[k]. Cela lèveKeyNotFoundException; utilisezTryGetValue. - Appeler
Addpour une clé qui existe peut-être. Cela lèveArgumentException; utilisez l'indexeur ouTryAdd. - Ajouter des clés dans un
foreachsur le dictionnaire. Cela lèveInvalidOperationException; 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.
ContainsKeypuis l'indexeur. Deux recherches ;TryGetValuen'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).