web-dev-qa-db-fra.com

Dictionnaire retournant une valeur par défaut si la clé n'existe pas

Je me retrouve souvent avec le modèle actuel dans mon code

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

Ou parfois

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

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

Ces deux manières se sentent tout à fait détournées. Ce que j'aimerais vraiment, c'est quelque chose comme

dictionary.GetValueOrDefault(key)

Maintenant, je pourrais écrire une méthode d'extension pour la classe de dictionnaire qui le fait pour moi, mais je me suis dit qu'il me manquait peut-être quelque chose qui existe déjà. SO, y a-t-il un moyen de faire cela d'une manière plus "douce pour les yeux" sans écrire une méthode d'extension au dictionnaire?

209
wasatz

TryGetValue assignera déjà la valeur par défaut pour le type au dictionnaire, vous pouvez donc simplement utiliser:

dictionary.TryGetValue(key, out value);

et juste ignorer la valeur de retour. Cependant, cela retournera retournera simplement default(TValue), pas une valeur par défaut personnalisée (ni, plus utilement, le résultat de l’exécution d’un délégué). Il n'y a rien de plus puissant intégré dans le cadre. Je suggérerais deux méthodes d'extension:

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();
}

(Vous voudrez peut-être mettre l'argument en vérifiant, bien sûr :)

269
Jon Skeet

Je sais que ceci est un ancien post et je suis favorable aux méthodes d’extension, mais voici une classe simple que j’utilise de temps en temps pour gérer les dictionnaires lorsque j’ai besoin de valeurs par défaut.

J'aimerais que cela fasse simplement partie de la classe de base Dictionary.

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; }
  }
}

Attention cependant. En sous-classant et en utilisant new (puisque override n'est pas disponible sur le type natif Dictionary,), si un objet DictionaryWithDefault est converti en amont en un simple Dictionary, appelant l'indexeur utilisera l'implémentation base Dictionary (en levant une exception si elle manque) plutôt que l'implémentation de la sous-classe.

28
roberocity

J'ai créé un DefaultableDictionary pour faire exactement ce que vous demandez!

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);
        }
    }
}

Ce projet est un simple décorateur pour un objet IDictionary et une méthode d’extension facilitant son utilisation.

DefaultableDictionary permettra de créer un wrapper autour d'un dictionnaire fournissant une valeur par défaut lors d'une tentative d'accès à une clé inexistante ou d'une énumération de toutes les valeurs d'un IDictionary.

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

Blog post sur l'utilisation aussi bien.

23
John Sonmez

Non, rien de tel n'existe. La méthode d'extension est la voie à suivre, et votre nom pour elle (GetValueOrDefault) est un très bon choix.

4
Patrick Karcher