¿Hay una implementación IDictionary que, en la clave que falta, devuelve el valor predeterminado en lugar de arrojar?

El indexador en Dictionary arroja una excepción si falta la clave. ¿Hay una implementación de IDictionary que en su lugar devolverá el valor predeterminado (T)?

Sé sobre el método “TryGetValue”, pero eso es imposible de usar con linq.

¿Esto haría eficientemente lo que necesito ?:

myDict.FirstOrDefault(a => a.Key == someKeyKalue); 

No creo que vaya a ser así, ya que creo que iterará las teclas en lugar de usar una búsqueda Hash.

De hecho, eso no será eficiente en absoluto.

Siempre puedes escribir un método de extensión:

 public static TValue GetValueOrDefault (this IDictionary dictionary, TKey key) { TValue ret; // Ignore return value dictionary.TryGetValue(key, out ret); return ret; } 

O con C # 7.1:

 public static TValue GetValueOrDefault (this IDictionary dictionary, TKey key) => dictionary.TryGetValue(key, out var ret) ? ret : default; 

Que usa:

  • Un método con cuerpo de expresión (C # 6)
  • Una variable de salida (C # 7.0)
  • Un literal predeterminado (C # 7.1)

Llevar estos métodos de extensión puede ayudar …

 public static V GetValueOrDefault(this IDictionary dict, K key) { return dict.GetValueOrDefault(key, default(V)); } public static V GetValueOrDefault(this IDictionary dict, K key, V defVal) { return dict.GetValueOrDefault(key, () => defVal); } public static V GetValueOrDefault(this IDictionary dict, K key, Func defValSelector) { V value; return dict.TryGetValue(key, out value) ? value : defValSelector(); } 

Collections.Specialized.StringDictionary proporciona un resultado sin excepción cuando se busca el valor de una clave faltante. Tampoco distingue entre mayúsculas y minúsculas por defecto.

Advertencias

Solo es válido para sus usos especializados y, al estar diseñado antes de los generics, no tiene un buen enumerador si necesita revisar toda la colección.

Uno podría definir una interfaz para la función de búsqueda de teclas de un diccionario. Probablemente lo definiría como algo así como:

 Interface IKeyLookup(Of Out TValue) Function Contains(Key As Object) Function GetValueIfExists(Key As Object) As TValue Function GetValueIfExists(Key As Object, ByRef Succeeded As Boolean) As TValue End Interface Interface IKeyLookup(Of In TKey, Out TValue) Inherits IKeyLookup(Of Out TValue) Function Contains(Key As TKey) Function GetValue(Key As TKey) As TValue Function GetValueIfExists(Key As TKey) As TValue Function GetValueIfExists(Key As TKey, ByRef Succeeded As Boolean) As TValue End Interface 

La versión con claves no genéricas permitiría que el código que utilizaba código utilizara tipos de clave no estructurales para permitir la varianza de clave arbitraria, lo que no sería posible con un parámetro de tipo genérico. No se debe permitir el uso de un Dictionary(Of Cat, String) mutable Dictionary(Of Cat, String) como Dictionary(Of Animal, String) mutable Dictionary(Of Animal, String) ya que este último permitiría SomeDictionaryOfCat.Add(FionaTheFish, "Fiona") . Pero no hay nada de malo con el uso de un Dictionary(Of Cat, String) mutable Dictionary(Of Cat, String) como un Dictionary(Of Animal, String) inmutable Dictionary(Of Animal, String) , ya que SomeDictionaryOfCat.Contains(FionaTheFish) debe considerar una expresión perfectamente bien formada (debe devolver false , sin tener para buscar en el diccionario, para cualquier cosa que no sea del tipo Cat ).

Desafortunadamente, la única manera en que uno realmente podrá usar dicha interfaz es si uno envuelve un objeto de Dictionary en una clase que implementa la interfaz. Dependiendo de lo que esté haciendo, sin embargo, dicha interfaz y la variación que permite podrían hacer que valga la pena el esfuerzo.

Si está utilizando ASP.NET MVC, puede aprovechar la clase RouteValueDictionary que hace el trabajo.

 public object this[string key] { get { object obj; this.TryGetValue(key, out obj); return obj; } set { this._dictionary[key] = value; } } 
 public class DefaultIndexerDictionary : IDictionary { private IDictionary _dict = new Dictionary(); public TValue this[TKey key] { get { TValue val; if (!TryGetValue(key, out val)) return default(TValue); return val; } set { _dict[key] = value; } } public ICollection Keys => _dict.Keys; public ICollection Values => _dict.Values; public int Count => _dict.Count; public bool IsReadOnly => _dict.IsReadOnly; public void Add(TKey key, TValue value) { _dict.Add(key, value); } public void Add(KeyValuePair item) { _dict.Add(item); } public void Clear() { _dict.Clear(); } public bool Contains(KeyValuePair item) { return _dict.Contains(item); } public bool ContainsKey(TKey key) { return _dict.ContainsKey(key); } public void CopyTo(KeyValuePair[] array, int arrayIndex) { _dict.CopyTo(array, arrayIndex); } public IEnumerator> GetEnumerator() { return _dict.GetEnumerator(); } public bool Remove(TKey key) { return _dict.Remove(key); } public bool Remove(KeyValuePair item) { return _dict.Remove(item); } public bool TryGetValue(TKey key, out TValue value) { return _dict.TryGetValue(key, out value); } IEnumerator IEnumerable.GetEnumerator() { return _dict.GetEnumerator(); } } 

Esta pregunta ayudó a confirmar que TryGetValue juega aquí el rol de FirstOrDefault .

Una característica interesante de C # 7 que me gustaría mencionar es la función de variables de salida , y si agrega el operador condicional nulo de C # 6 a la ecuación, su código podría ser mucho más simple sin necesidad de métodos de extensión adicionales.

 var dic = new Dictionary(); dic.TryGetValue("Test", out var item); item?.DoSomething(); 

La desventaja de esto es que no puedes hacer todo en línea así;

 dic.TryGetValue("Test", out var item)?.DoSomething(); 

Si necesitamos / queremos hacer esto, debemos codificar un método de extensión como el de Jon.

Aquí hay una versión de @ JonSkeet’s para el mundo de C # 7.1 que también permite pasar un valor predeterminado opcional:

 public static TV GetValueOrDefault(this IDictionary dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue; 

Puede ser más eficiente tener dos funciones para optimizar el caso en el que desea devolver el default(TV) :

 public static TV GetValueOrDefault(this IDictionary dict, TK key, TV defaultValue) => dict.TryGetValue(key, out TV value) ? value : defaultValue; public static TV GetValueOrDefault2(this IDictionary dict, TK key) { dict.TryGetValue(key, out TV value); return value; } 

Desafortunadamente C # no tiene (¿todavía?) Un operador de coma (o el operador de punto y coma propuesto C # 6) por lo que debe tener un cuerpo de función real (¡jadeo!) Para una de las sobrecargas.

Usé la encapsulación para crear un IDictionary con un comportamiento muy similar a un mapa STL , para aquellos de ustedes que están familiarizados con c ++. Para aquellos que no son:

  • indexer get {} en SafeDictionary a continuación devuelve el valor predeterminado si una clave no está presente, y agrega esa clave al diccionario con un valor predeterminado. A menudo, este es el comportamiento deseado, ya que está buscando elementos que aparecerán con el tiempo o que tendrán una buena oportunidad de aparecer.
  • El método Add (clave TK, TV val) se comporta como un método AddOrUpdate, reemplazando el valor presente si existe en lugar de arrojar. No veo por qué m $ no tiene un método AddOrUpdate y piensa que lanzar errores en escenarios muy comunes es una buena idea.

TL / DR: SafeDictionary está escrito para no arrojar excepciones bajo ninguna circunstancia, salvo escenarios perversos , como que la computadora no tenga memoria (o esté en llamas). Lo hace al reemplazar el comportamiento Agregar con AddOrUpdate y devolver el valor predeterminado en lugar de arrojar NotFoundException del indexador.

Aquí está el código:

 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; public class SafeDictionary: IDictionary { Dictionary _underlying = new Dictionary(); public ICollection Keys => _underlying.Keys; public ICollection Values => _underlying.Values; public int Count => _underlying.Count; public bool IsReadOnly => false; public TD this[TK index] { get { TD data; if (_underlying.TryGetValue(index, out data)) { return data; } _underlying[index] = default(TD); return default(TD); } set { _underlying[index] = value; } } public void CopyTo(KeyValuePair[] array, int arrayIndex) { Array.Copy(_underlying.ToArray(), 0, array, arrayIndex, Math.Min(array.Length - arrayIndex, _underlying.Count)); } public void Add(TK key, TD value) { _underlying[key] = value; } public void Add(KeyValuePair item) { _underlying[item.Key] = item.Value; } public void Clear() { _underlying.Clear(); } public bool Contains(KeyValuePair item) { return _underlying.Contains(item); } public bool ContainsKey(TK key) { return _underlying.ContainsKey(key); } public IEnumerator> GetEnumerator() { return _underlying.GetEnumerator(); } public bool Remove(TK key) { return _underlying.Remove(key); } public bool Remove(KeyValuePair item) { return _underlying.Remove(item.Key); } public bool TryGetValue(TK key, out TD value) { return _underlying.TryGetValue(key, out value); } IEnumerator IEnumerable.GetEnumerator() { return _underlying.GetEnumerator(); } } 

No, porque de lo contrario, ¿cómo sabría la diferencia cuando la clave existe pero almacenó un valor nulo? Eso podría ser significativo.

¿Qué pasa con este one-liner que verifica si una clave está presente usando ContainsKey y luego devuelve el valor normalmente recuperado o el valor predeterminado utilizando el operador condicional?

 var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue; 

No es necesario implementar una nueva clase de diccionario que admita valores predeterminados, simplemente reemplace sus declaraciones de búsqueda con la línea corta arriba.