2010-04-08 16 views
165

Me encuentro con el patrón actual con bastante frecuencia en mi código en la actualidadDiccionario devolver un valor predeterminado si la clave no existe

var dictionary = new Dictionary<type, IList<othertype>>(); 
// Add stuff to dictionary 

var somethingElse = dictionary.ContainsKey(key) ? dictionary[key] : new List<othertype>(); 
// Do work with the somethingelse variable 

O a veces

var dictionary = new Dictionary<type, IList<othertype>>(); 
// Add stuff to dictionary 

IList<othertype> somethingElse; 
if(!dictionary.TryGetValue(key, out somethingElse) { 
    somethingElse = new List<othertype>(); 
} 

Ambas formas se sienten bastante rotonda . Lo que realmente me gustaría es algo así como

dictionary.GetValueOrDefault(key) 

Ahora, podría escribir un método de extensión para la clase de diccionario que hace esto para mí, pero pensé que podría estar pasando algo que ya existe. Entonces, ¿hay alguna forma de hacer esto de una manera que sea más "fácil de entender" sin escribir un método de extensión en el diccionario?

+0

No estoy seguro de por qué todas las respuestas a continuación son tan complejas. Solo use el operador coalescente: 'string valFromDict = someDict [" someKey "] ?? "someDefaultVal"; ' –

+0

@DylanHunt Aunque eso no funciona para los tipos de valores. ;) – wasatz

Respuesta

218

TryGetValue ya se asignará el valor predeterminado para el tipo de diccionario, por lo que sólo se puede utilizar:

dictionary.TryGetValue(key, out value); 

y simplemente ignorar el valor de retorno. Sin embargo, eso realmente será simplemente devuelva default(TValue), no algún valor predeterminado personalizado (ni, más útilmente, el resultado de ejecutar un delegado). No hay nada más poderoso incorporado en el marco. Yo sugeriría dos métodos de extensión:

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, 
    TKey key, 
    TValue defaultValue) 
{ 
    TValue value; 
    return dictionary.TryGetValue(key, out value) ? value : defaultValue; 
} 

public static TValue GetValueOrDefault<TKey, TValue> 
    (this IDictionary<TKey, TValue> dictionary, 
    TKey key, 
    Func<TValue> defaultValueProvider) 
{ 
    TValue value; 
    return dictionary.TryGetValue(key, out value) ? value 
     : defaultValueProvider(); 
} 

(Es posible que desee poner en comprobación de argumentos, por supuesto :)

+0

Para abrir una lombriz de gusano, estas extensiones funcionan cuando la clave no existe, pero se lanza TryGetValue si la clave es nula. Saber si la clave es nulable o no lo haría sería una verificación trivial. – ProfK

+2

@ProfK: simplemente puede comparar la clave con 'null' de todos modos; cuando TKey es un tipo de valor que no admite nulos, siempre devolverá falso. Personalmente, no creo que me gustaría hacer eso, las claves nulas * casi * siempre representan un error, en mi experiencia. –

+0

Lo hacen. Tan pronto como probé tu código, los datos desagradables con los que estoy tratando presentaron una clave nula, pero no puedo cambiar eso ahora. – ProfK

4

No, no existe nada por el estilo. El método de extensión es el camino a seguir, y su nombre (GetValueOrDefault) es una muy buena opción.

20

Creé DefaultableDictionary para hacer exactamente lo que estás pidiendo!

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 

namespace DefaultableDictionary { 
    public class DefaultableDictionary<TKey, TValue> : IDictionary<TKey, TValue> { 
     private readonly IDictionary<TKey, TValue> dictionary; 
     private readonly TValue defaultValue; 

     public DefaultableDictionary(IDictionary<TKey, TValue> dictionary, TValue defaultValue) { 
      this.dictionary = dictionary; 
      this.defaultValue = defaultValue; 
     } 

     public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { 
      return dictionary.GetEnumerator(); 
     } 

     IEnumerator IEnumerable.GetEnumerator() { 
      return GetEnumerator(); 
     } 

     public void Add(KeyValuePair<TKey, TValue> item) { 
      dictionary.Add(item); 
     } 

     public void Clear() { 
      dictionary.Clear(); 
     } 

     public bool Contains(KeyValuePair<TKey, TValue> item) { 
      return dictionary.Contains(item); 
     } 

     public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { 
      dictionary.CopyTo(array, arrayIndex); 
     } 

     public bool Remove(KeyValuePair<TKey, TValue> item) { 
      return dictionary.Remove(item); 
     } 

     public int Count { 
      get { return dictionary.Count; } 
     } 

     public bool IsReadOnly { 
      get { return dictionary.IsReadOnly; } 
     } 

     public bool ContainsKey(TKey key) { 
      return dictionary.ContainsKey(key); 
     } 

     public void Add(TKey key, TValue value) { 
      dictionary.Add(key, value); 
     } 

     public bool Remove(TKey key) { 
      return dictionary.Remove(key); 
     } 

     public bool TryGetValue(TKey key, out TValue value) { 
      if (!dictionary.TryGetValue(key, out value)) { 
       value = defaultValue; 
      } 

      return true; 
     } 

     public TValue this[TKey key] { 
      get 
      { 
       try 
       { 
        return dictionary[key]; 
       } catch (KeyNotFoundException) { 
        return defaultValue; 
       } 
      } 

      set { dictionary[key] = value; } 
     } 

     public ICollection<TKey> Keys { 
      get { return dictionary.Keys; } 
     } 

     public ICollection<TValue> Values { 
      get 
      { 
       var values = new List<TValue>(dictionary.Values) { 
        defaultValue 
       }; 
       return values; 
      } 
     } 
    } 

    public static class DefaultableDictionaryExtensions { 
     public static IDictionary<TKey, TValue> WithDefaultValue<TValue, TKey>(this IDictionary<TKey, TValue> dictionary, TValue defaultValue) { 
      return new DefaultableDictionary<TKey, TValue>(dictionary, defaultValue); 
     } 
    } 
} 

Este proyecto es un simple decorador para un objeto IDictionary y un método de extensión para que sea fácil de usar.

DefaultableDictionary permitirá crear un contenedor alrededor de un diccionario que proporciona un valor predeterminado cuando se intenta acceder a una clave que no existe o enumerar a través de todos los valores en un IDictionary.

Ejemplo: var dictionary = new Dictionary<string, int>().WithDefaultValue(5);

Blog post en el uso, así.

+0

Proporcione el contexto que respalda el enlace github directamente en su respuesta, o probablemente será eliminado. –

+2

Sería útil si no eliminas los enlaces que también proporcionan claridad. –

+5

me gustaría escribir el captador índice como: ' TValue pública esta [clave TKey] { obtener {valor \t \t TValue; \t \t TryGetValue (clave, valor de salida); \t \t valor de retorno; } ' Para evitar el manejo de excepciones. –

22

Sé que esta es una publicación anterior y prefiero los métodos de extensión, pero esta es una clase simple que uso de vez en cuando para manejar diccionarios cuando necesito valores predeterminados.

Ojalá fuera parte de la clase básica de diccionario.

public class DictionaryWithDefault<TKey, TValue> : Dictionary<TKey, TValue> 
{ 
    TValue _default; 
    public TValue DefaultValue { 
    get { return _default; } 
    set { _default = value; } 
    } 
    public DictionaryWithDefault() : base() { } 
    public DictionaryWithDefault(TValue defaultValue) : base() { 
    _default = defaultValue; 
    } 
    public new TValue this[TKey key] 
    { 
    get { 
     TValue t; 
     return base.TryGetValue(key, out t) ? t : _default; 
    } 
    set { base[key] = value; } 
    } 
} 

Ten cuidado, sin embargo. Subclasificando y el uso de new (ya override no está disponible en el nativo Dictionary tipo), si un objeto DictionaryWithDefault es upcast a una llanura Dictionary, llamando al indexador utilizará la implementación base Dictionary (lanzar una excepción si faltan) en lugar de la aplicación de la subclase .

+0

Use trygetvalue better .. – nawfal

+0

trygetvalue no le permite especificar un valor predeterminado, es decir, para un valor de cadena, puede querer que vuelva" "en su lugar of null – katbyte

+0

@nawfal TryGetValue se debe usar cada vez que solicite un valor en el diccionario. Este envoltorio simple significa que puedo pedir un valor y devolverá el valor o el predeterminado que especifiqué en el constructor. No tengo que recordar usar un método especial. – roberocity

Cuestiones relacionadas