A menudo me encuentro buscando preguntas en línea, y muchas soluciones incluyen diccionarios. Sin embargo, cada vez que trato de implementarlos, obtengo este horrible hedor en mi código. Por ejemplo, cada vez que quiero usar un valor:
int x;
if (dict.TryGetValue("key", out x)) {
DoSomethingWith(x);
}
Esas son 4 líneas de código para hacer esencialmente lo siguiente: DoSomethingWith(dict["key"])
Escuché que usar la palabra clave out es un anti patrón porque hace que las funciones muten sus parámetros.
Además, a menudo necesito un diccionario "invertido", donde volteo las claves y los valores.
Del mismo modo, a menudo me gustaría recorrer los elementos de un diccionario y convertir las claves o valores en listas, etc. para hacerlo mejor.
Siento que casi siempre hay una forma mejor y más elegante de usar los diccionarios, pero estoy perdido.
System.Collections.Generic
espacio de nombres no son seguras para subprocesos .Dictionary
, lo que realmente quería era una nueva clase. Para esos 50% (solo), es un olor de diseño.Respuestas:
Los diccionarios (C # o de otra manera) son simplemente un contenedor donde busca un valor basado en una clave. En muchos idiomas, se identifica más correctamente como un mapa, siendo la implementación más común un HashMap.
El problema a considerar es qué sucede cuando una clave no existe. Algunos idiomas se comportan mediante la devolución
null
onil
o algún otro valor equivalente. En silencio, el valor predeterminado es un valor en lugar de informarle que un valor no existe.Para bien o para mal, los diseñadores de la biblioteca de C # idearon un modismo para lidiar con el comportamiento. Razonaron que el comportamiento predeterminado para buscar un valor que no existe es lanzar una excepción. Si desea evitar excepciones, puede usar la
Try
variante. Es el mismo enfoque que utilizan para analizar cadenas en enteros u objetos de fecha / hora. Esencialmente, el impacto es así:Y eso se trasladó al diccionario, cuyo indexador delega a
Get(index)
:Esta es simplemente la forma en que se diseña el lenguaje.
¿Deben
out
desalentarse las variables?C # no es el primer idioma que los tiene, y tienen su propósito en situaciones específicas. Si está tratando de construir un sistema altamente concurrente, entonces no puede usar
out
variables en los límites de concurrencia.En muchos sentidos, si hay un idioma que es adoptado por el idioma y los proveedores de la biblioteca central, trato de adoptar esos idiomas en mis API. Eso hace que la API se sienta más consistente y como en casa en ese idioma. Entonces, un método escrito en Ruby no se verá como un método escrito en C #, C o Python. Cada uno tiene una forma preferida de construir código, y trabajar con eso ayuda a los usuarios de su API a aprenderlo más rápidamente.
¿Son los mapas en general un antipatrón?
Tienen su propósito, pero muchas veces pueden ser la solución incorrecta para el propósito que usted tiene. Particularmente si tiene un mapeo bidireccional que necesita. Hay muchos contenedores y formas de organizar los datos. Hay muchos enfoques que puede usar, y a veces necesita pensar un poco antes de elegir ese contenedor.
Si tiene una lista muy corta de valores de mapeo bidireccional, es posible que solo necesite una lista de tuplas. O una lista de estructuras, donde puede encontrar fácilmente la primera coincidencia a cada lado de la asignación.
Piense en el dominio del problema y elija la herramienta más adecuada para el trabajo. Si no hay uno, créelo.
fuente
Option<T>
, entonces creo que eso haría que el método sea mucho más difícil de usar en C # 2.0, porque no tenía coincidencia de patrones.Algunas buenas respuestas aquí sobre los principios generales de las tablas hash / diccionarios. Pero pensé que tocaría tu ejemplo de código,
A partir de C # 7 (que creo que tiene alrededor de dos años), eso se puede simplificar para:
Y, por supuesto, podría reducirse a una línea:
Si tiene un valor predeterminado para cuando la clave no existe, puede convertirse en:
Para que pueda lograr formas compactas utilizando adiciones de lenguaje razonablemente recientes.
fuente
"key"
no está presente,x
se inicializará adefault(TValue)
"key".DoSomethingWithThis()
getOrElse("key", defaultValue)
objeto nulo sigue siendo mi patrón favorito. Trabaja de esa manera y no te importa siTryGetValue
devuelve verdadero o falso.TryGetValue
no es atómico / seguro para subprocesos, por lo que podría realizar fácilmente una verificación de existencia y otra para obtener y operar sobre el valorEsto no es un olor a código ni un antipatrón, ya que el uso de funciones de estilo TryGet con un parámetro de salida es idiomático C #. Sin embargo, hay 3 opciones proporcionadas en C # para trabajar con un diccionario, por lo que debe estar seguro de que está utilizando la correcta para su situación. Creo que sé de dónde proviene el rumor de que hay un problema al usar un parámetro out, así que lo manejaré al final.
Qué características usar cuando se trabaja con un diccionario C #:
Para justificar esto, solo es necesario consultar la documentación del Diccionario TryGetValue, en "Observaciones" :
La razón por la que existe TryGetValue es para actuar como una forma más conveniente de usar ContainsKey y Item [TKey], evitando tener que buscar el diccionario dos veces, por lo que fingir que no existe y hacer las dos cosas que hace manualmente es bastante incómodo. elección.
En la práctica, rara vez he usado un diccionario sin formato, debido a esta máxima simple: elija la clase / contenedor más genérico que le brinde la funcionalidad que necesita . El diccionario no fue diseñado para buscar por valor en lugar de por clave (por ejemplo), por lo que si eso es algo que desea, puede tener más sentido usar una estructura alternativa. Creo que pude haber usado Dictionary una vez en el proyecto de desarrollo de un año que hice, simplemente porque rara vez era la herramienta adecuada para el trabajo que estaba tratando de hacer. El diccionario ciertamente no es la navaja suiza de la caja de herramientas C #.
¿Qué hay de malo con nuestros parámetros?
CA1021: evitar parámetros
Supongo que ahí es donde escuchaste que nuestros parámetros eran algo así como un antipatrón. Como con todas las reglas, debe leer más de cerca para entender el "por qué", y en este caso incluso hay una mención explícita de cómo el patrón Try no viola la regla :
fuente
Faltan al menos dos métodos en los diccionarios de C # que, en mi opinión, limpian el código considerablemente en muchas situaciones en otros lenguajes. El primero es devolver un
Option
, que le permite escribir código como el siguiente en Scala:El segundo es devolver un valor predeterminado especificado por el usuario si no se encuentra la clave:
Hay algo que decir sobre el uso de las expresiones idiomáticas que proporciona un idioma cuando es apropiado, como el
Try
patrón, pero eso no significa que solo tenga que usar lo que proporciona el idioma. Somos programadores Está bien crear nuevas abstracciones para que nuestra situación específica sea más fácil de entender, especialmente si elimina muchas repeticiones. Si con frecuencia necesita algo, como búsquedas inversas o iterar a través de valores, haga que suceda. Crea la interfaz que desearías tener.fuente
Estoy de acuerdo en que esto es poco elegante. Un mecanismo que me gusta usar en este caso, donde el valor es un tipo de estructura, es:
Ahora tenemos una nueva versión de los
TryGetValue
retornosint?
. Entonces podemos hacer un truco similar para extenderT?
:Y ahora póngalo junto:
y nos queda solo una declaración clara y clara.
Estaría un poco menos redactado, y diría que evitar mutaciones cuando sea posible es una buena idea.
Luego implemente u obtenga un diccionario bidireccional. Son fáciles de escribir, o hay muchas implementaciones disponibles en Internet. Aquí hay un montón de implementaciones, por ejemplo:
/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp
Claro, todos lo hacemos.
Pregúntese "suponga que tengo una clase distinta a la
Dictionary
que implementó la operación exacta que deseo hacer; ¿cómo sería esa clase?" Luego, una vez que haya respondido esa pregunta, implemente esa clase . Eres un programador de computadoras. ¡Resuelve tus problemas escribiendo programas de computadora!fuente
Action<T>
que es muy similar ainterface IAction<T> { void Invoke(T t); }
, pero con reglas muy indulgentes sobre qué "implementa" esa interfaz y cómoInvoke
se puede invocar. Si quiere saber más, aprenda sobre "delegados" en C #, y luego aprenda sobre expresiones lambda.La
TryGetValue()
construcción solo es necesaria si no sabe si "clave" está presente como una clave dentro del diccionario o no, de lo contrarioDoSomethingWith(dict["key"])
es perfectamente válido.Un enfoque "menos sucio" podría ser utilizarlo
ContainsKey()
como un cheque.fuente
TryGetValue
es, al menos hace que sea difícil olvidar manejar el caso vacío. Idealmente, desearía que esto solo devuelva un Opcional.ContainsKey
enfoque para las aplicaciones multiproceso, que es la vulnerabilidad de tiempo de verificación al tiempo de uso (TOCTOU). ¿Qué pasa si algún otro hilo elimina la clave entre las llamadas aContainsKey
yGetValue
?Optional<Optional<T>>
TryGetValue
enConcurrentDictionary
. El diccionario regular no se sincronizaríaOtras respuestas contienen grandes puntos, por lo que no las volveré a exponer aquí, sino que me centraré en esta parte, que parece ser ignorada hasta ahora:
En realidad, es bastante fácil iterar sobre un diccionario, ya que implementa
IEnumerable
:Si prefiere Linq, eso también funciona bien:
En general, no creo que los diccionarios sean un antipatrón: son solo una herramienta específica con usos específicos.
Además, para obtener más detalles, consulte
SortedDictionary
(utiliza árboles RB para un rendimiento más predecible) ySortedList
(que también es un diccionario confusamente llamado que sacrifica la velocidad de inserción por la velocidad de búsqueda, pero brilla si está mirando con un pre fijo conjunto ordenado). He tenido un caso en el que reemplazar unDictionary
con unSortedDictionary
resultado en una ejecución de orden de magnitud más rápida (pero también podría ocurrir al revés).fuente
Si cree que usar un diccionario es incómodo, puede que no sea la opción correcta para su problema. Los diccionarios son geniales, pero como notó un comentarista, a menudo se usan como atajos para algo que debería haber sido una clase. O el diccionario en sí puede ser correcto como un método de almacenamiento central, pero debe haber una clase de envoltura alrededor para proporcionar los métodos de servicio deseados.
Mucho se ha dicho sobre Dict [clave] versus TryGet. Lo que uso mucho es iterar sobre el diccionario usando KeyValuePair. Aparentemente, esta es una construcción menos conocida.
La bendición principal de un diccionario es que es realmente rápido en comparación con otras colecciones a medida que aumenta el número de elementos. Si tiene que verificar si una clave existe mucho, puede preguntarse si su uso de un diccionario es apropiado. Como cliente, normalmente debe saber qué pone allí y, por lo tanto, qué sería seguro consultar.
fuente