Je me retrouve souvent à chercher des questions en ligne, et de nombreuses solutions incluent des dictionnaires. Cependant, chaque fois que j'essaie de les implémenter, j'obtiens cette horrible odeur dans mon code. Par exemple, chaque fois que je veux utiliser une valeur:
int x;
if (dict.TryGetValue("key", out x)) {
DoSomethingWith(x);
}
C'est 4 lignes de code pour faire essentiellement ce qui suit: DoSomethingWith(dict["key"])
J'ai entendu dire que l'utilisation du mot-clé out est un anti-modèle car il fait que les fonctions modifient leurs paramètres.
En outre, je me retrouve souvent besoin d'un dictionnaire "inversé", où je retourne les clés et les valeurs.
De même, je voudrais souvent parcourir les éléments d'un dictionnaire et me retrouver à convertir des clés ou des valeurs en listes, etc. pour faire mieux.
J'ai l'impression qu'il y a presque toujours une meilleure façon, plus élégante, d'utiliser des dictionnaires, mais je suis à court.
System.Collections.Generic
espace de noms ne sont pas thread-safe .Dictionary
, ce qu'il voulait vraiment, c'était un nouveau cours. Pour ces 50% (seulement), c'est une odeur de design.Réponses:
Les dictionnaires (C # ou autre) sont simplement un conteneur où vous recherchez une valeur basée sur une clé. Dans de nombreuses langues, il est plus correctement identifié comme une carte, l'implémentation la plus courante étant un HashMap.
Le problème à considérer est ce qui se passe lorsqu'une clé n'existe pas. Certaines langues se comportent en retournant
null
ounil
ou une autre valeur équivalente. La valeur par défaut silencieuse à une valeur au lieu de vous informer qu'une valeur n'existe pas.Pour le meilleur ou pour le pire, les concepteurs de bibliothèques C # ont trouvé un idiome pour gérer le comportement. Ils ont estimé que le comportement par défaut pour rechercher une valeur qui n'existe pas est de lever une exception. Si vous souhaitez éviter les exceptions, vous pouvez utiliser la
Try
variante. C'est la même approche qu'ils utilisent pour analyser les chaînes en entiers ou en objets date / heure. Essentiellement, l'impact est le suivant:Et cela a été reporté au dictionnaire, dont l'indexeur délègue
Get(index)
:C'est simplement la façon dont la langue est conçue.
Devrait
out
il décourager les variables?C # n'est pas la première langue à les avoir, et ils ont leur raison d'être dans des situations spécifiques. Si vous essayez de créer un système hautement simultané, vous ne pouvez pas utiliser de
out
variables aux limites de concurrence.À bien des égards, s'il existe un idiome qui est adopté par les fournisseurs de langue et de bibliothèque de base, j'essaie d'adopter ces idiomes dans mes API. Cela rend l'API plus cohérente et à l'aise dans cette langue. Une méthode écrite en Ruby ne ressemblera donc pas à une méthode écrite en C #, C ou Python. Ils ont chacun une façon préférée de créer du code, et travailler avec cela aide les utilisateurs de votre API à l'apprendre plus rapidement.
Les cartes en général sont-elles un anti-modèle?
Ils ont leur but, mais souvent, ils peuvent être la mauvaise solution pour le but que vous avez. Surtout si vous avez besoin d'une cartographie bidirectionnelle. Il existe de nombreux conteneurs et façons d'organiser les données. Il existe de nombreuses approches que vous pouvez utiliser, et parfois vous devez réfléchir un peu avant de choisir ce conteneur.
Si vous avez une très courte liste de valeurs de mappage bidirectionnel, vous n'aurez peut-être besoin que d'une liste de tuples. Ou une liste de structures, où vous pouvez tout aussi facilement trouver la première correspondance de chaque côté du mappage.
Pensez au domaine problématique et choisissez l'outil le plus approprié pour le travail. S'il n'y en a pas, créez-le.
la source
Option<T>
, alors je pense que cela rendrait la méthode beaucoup plus difficile à utiliser en C # 2.0, car elle n'avait pas de correspondance de modèle.Quelques bonnes réponses ici sur les principes généraux des tables de hachage / dictionnaires. Mais je pensais que je toucherais à votre exemple de code,
À partir de C # 7 (qui, je pense, a environ deux ans), cela peut être simplifié pour:
Et bien sûr, il pourrait être réduit à une seule ligne:
Si vous avez une valeur par défaut lorsque la clé n'existe pas, elle peut devenir:
Vous pouvez donc obtenir des formulaires compacts en utilisant des ajouts de langage raisonnablement récents.
la source
"key"
n'est pas présent,x
sera initialisé àdefault(TValue)
"key".DoSomethingWithThis()
getOrElse("key", defaultValue)
objet Null est toujours mon motif préféré. Travaillez de cette façon et vous ne vous souciez pas si lesTryGetValue
retours sont vrais ou faux.TryGetValue
n'est pas atomique / thread-safe donc vous pouvez tout aussi facilement effectuer une vérification de l'existence et une autre pour saisir et opérer la valeurCe n'est ni une odeur de code ni un anti-modèle, car l'utilisation de fonctions de style TryGet avec un paramètre out est un C # idiomatique. Cependant, il y a 3 options fournies en C # pour travailler avec un dictionnaire, donc si vous êtes sûr d'utiliser le bon pour votre situation. Je pense que je sais d'où vient la rumeur d'un problème d'utilisation d'un paramètre de sortie, je vais donc gérer cela à la fin.
Quelles fonctionnalités utiliser lorsque vous travaillez avec un dictionnaire C #:
Pour justifier cela, il suffit de se référer à la documentation du dictionnaire TryGetValue, sous "Remarques" :
La raison principale pour laquelle TryGetValue existe est d'agir comme un moyen plus pratique d'utiliser ContainsKey et Item [TKey], tout en évitant d'avoir à rechercher deux fois dans le dictionnaire - donc prétendre qu'il n'existe pas et faire les deux choses qu'il fait manuellement est plutôt délicat. choix.
En pratique, j'ai rarement utilisé un dictionnaire brut, en raison de cette maxime simple: choisissez la classe / le conteneur le plus générique qui vous offre les fonctionnalités dont vous avez besoin . Le dictionnaire n'a pas été conçu pour rechercher par valeur plutôt que par clé (par exemple), donc si c'est quelque chose que vous voulez, il peut être plus judicieux d'utiliser une autre structure. Je pense que j'ai peut-être utilisé Dictionary une fois dans le dernier projet de développement d'un an que j'ai fait, simplement parce que c'était rarement le bon outil pour le travail que j'essayais de faire. Le dictionnaire n'est certainement pas le couteau suisse de la boîte à outils C #.
Quel est le problème avec nos paramètres?
CA1021: Évitez les paramètres
Je suppose que c'est là que vous avez entendu que les paramètres de sortie étaient quelque chose comme un anti-modèle. Comme pour toutes les règles, vous devriez lire de plus près pour comprendre le `` pourquoi '', et dans ce cas, il y a même une mention explicite de la façon dont le modèle Try ne viole pas la règle :
la source
Il manque au moins deux méthodes dans les dictionnaires C # qui, à mon avis, nettoient considérablement le code dans de nombreuses situations dans d'autres langues. La première renvoie un
Option
, qui vous permet d'écrire du code comme celui-ci dans Scala:La seconde renvoie une valeur par défaut spécifiée par l'utilisateur si la clé n'est pas trouvée:
Il y a quelque chose à dire pour l' utilisation des idiomes une langue fournit le cas échéant, comme le
Try
modèle, mais cela ne signifie pas que vous devez seulement utiliser ce que la langue offre. Nous sommes programmeurs. Il est normal de créer de nouvelles abstractions pour rendre notre situation spécifique plus facile à comprendre, surtout si cela élimine beaucoup de répétitions. Si vous avez fréquemment besoin de quelque chose, comme des recherches inversées ou une itération à travers des valeurs, faites-le. Créez l'interface que vous souhaitez avoir.la source
Je reconnais que cela n'est pas élégant. Un mécanisme que j'aime utiliser dans ce cas, où la valeur est un type struct, est:
Nous avons maintenant une nouvelle version de
TryGetValue
ce retourint?
. Nous pouvons alors faire une astuce similaire pour étendreT?
:Et maintenant, assemblez-le:
et nous en sommes à une seule déclaration claire.
Je serais un peu moins fortement formulé et je dirais qu'éviter les mutations lorsque c'est possible est une bonne idée.
Ensuite, implémentez ou obtenez un dictionnaire bidirectionnel. Ils sont simples à écrire, ou il existe de nombreuses implémentations disponibles sur Internet. Il y a un tas d'implémentations ici, par exemple:
/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp
Bien sûr, nous le faisons tous.
Demandez-vous "supposons que j'ai eu une classe autre que
Dictionary
celle qui a implémenté l'opération exacte que je souhaite faire; à quoi ressemblerait cette classe?" Ensuite, une fois que vous avez répondu à cette question, implémentez cette classe . Vous êtes programmeur informatique. Résolvez vos problèmes en écrivant des programmes informatiques!la source
Action<T>
à être très similaire àinterface IAction<T> { void Invoke(T t); }
, mais avec des règles très clémentes sur ce qui "implémente" cette interface et sur la façon dont elleInvoke
peut être invoquée. Si vous voulez en savoir plus, découvrez les «délégués» en C #, puis découvrez les expressions lambda.La
TryGetValue()
construction n'est nécessaire que si vous ne savez pas si "clé" est présente ou non dans le dictionnaire, sinon elleDoSomethingWith(dict["key"])
est parfaitement valide.Une approche «moins sale» pourrait être plutôt utilisée
ContainsKey()
comme chèque.la source
TryGetValue
, au moins, il est difficile d'oublier de gérer le cas vide. Idéalement, je souhaite que cela renvoie simplement une option.ContainsKey
approche pour les applications multithread, qui est la vulnérabilité de temps de vérification à l'heure d'utilisation (TOCTOU). Que faire si un autre thread supprime la clé entre les appels àContainsKey
etGetValue
?Optional<Optional<T>>
TryGetValue
deConcurrentDictionary
. Le dictionnaire régulier ne serait pas synchroniséD'autres réponses contiennent d'excellents points, donc je ne les reformulerai pas ici, mais je me concentrerai plutôt sur cette partie, qui semble avoir été largement ignorée jusqu'à présent:
En fait, il est assez facile d'itérer sur un dictionnaire, car il implémente
IEnumerable
:Si vous préférez Linq, cela fonctionne aussi très bien:
Dans l'ensemble, je ne trouve pas que les dictionnaires soient un contre-modèle - ils sont juste un outil spécifique avec des utilisations spécifiques.
En outre, pour encore plus de détails, consultez
SortedDictionary
(utilise les arbres RB pour des performances plus prévisibles) etSortedList
(qui est également un dictionnaire nommé de manière confuse qui sacrifie la vitesse d'insertion pour la vitesse de recherche, mais brille si vous regardez avec un fixe, pré- ensemble trié). J'ai eu un cas où le remplacement d'unDictionary
avec un aSortedDictionary
entraîné une exécution plus rapide d'un ordre de grandeur (mais cela pourrait aussi se produire dans l'autre sens).la source
Si vous pensez que l'utilisation d'un dictionnaire est gênant, ce n'est peut-être pas le bon choix pour votre problème. Les dictionnaires sont excellents, mais comme l'a remarqué un commentateur, ils sont souvent utilisés comme raccourci pour quelque chose qui aurait dû être une classe. Ou le dictionnaire lui-même peut être une méthode de stockage de base, mais il devrait y avoir une classe wrapper autour de lui pour fournir les méthodes de service souhaitées.
On a beaucoup parlé de Dict [clé] contre TryGet. Ce que j'utilise beaucoup, c'est d'itérer sur le dictionnaire en utilisant KeyValuePair. Apparemment, c'est une construction moins connue.
L'avantage principal d'un dictionnaire est qu'il est vraiment rapide par rapport aux autres collections à mesure que le nombre d'éléments augmente. Si vous devez vérifier si une clé existe beaucoup, vous pouvez vous demander si votre utilisation d'un dictionnaire est appropriée. En tant que client, vous devez généralement savoir ce que vous y mettez et donc ce qu'il serait sûr d'interroger.
la source