261 votes

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

Je me retrouve souvent à utiliser le motif actuel dans mon code de nos jours

var dictionnaire = new Dictionary>();
// Ajouter des éléments au dictionnaire

var autreChose = dictionnaire.ContainsKey(clé) ? dictionnaire[clé] : new List();
// Faire des choses avec la variable autreChose

Ou parfois

var dictionnaire = new Dictionary>();
// Ajouter des éléments au dictionnaire

IList autreChose;
if (!dictionnaire.TryGetValue(clé, out autreChose)) {
    autreChose = new List();
}

Ces deux façons semblent un peu détournées. Ce que je voudrais vraiment, c'est quelque chose comme

dictionnaire.GetValueOrDefault(clé)

Maintenant, je pourrais écrire une méthode d'extension pour la classe de dictionnaire qui le fait pour moi, mais je me suis dit que je pourrais passer à côté de quelque chose qui existe déjà. Alors, y a-t-il un moyen de faire cela d'une manière qui soit plus "agréable à regarder" sans écrire de méthode d'extension pour le dictionnaire?

0 votes

Pas sûr pourquoi toutes les réponses ci-dessous sont si complexes. Il suffit d'utiliser l'opérateur de coalescence : string valFromDict = someDict["someKey"] ?? "someDefaultVal";

1 votes

@DylanHunt Cela ne fonctionne pas pour les types de valeur. ;)

0 votes

Notez également que l'utilisation de l'opérateur [] peut avoir pour effet secondaire non désiré d'ajouter une valeur au dictionnaire si elle n'existe pas.

356voto

Jon Skeet Points 692016

Conformément aux commentaires, dans .Net Core 2+ / NetStandard 2.1+ / Net 5, MS a ajouté la méthode d'extension GetValueOrDefault()


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 ignorer simplement la valeur de retour. Cependant, cela renverra vraiment default(TValue), et non une valeur par défaut personnalisée (ou, de manière plus utile, le résultat de l'exécution d'un délégué). Il n'y a rien de plus puissant intégré dans le framework. Je suggérerais deux méthodes d'extension :

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

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

(Vous voudrez peut-être ajouter des vérifications des arguments, bien sûr :)

1 votes

Pour ouvrir une boîte de vers, ces extensions fonctionnent lorsque la clé n'existe pas, mais TryGetValue lance une erreur si la clé est nulle. Savoir si la clé est nullable ou non faciliterait cette vérification.

4 votes

@ProfK: Vous pouvez simplement comparer la clé avec null de toute façon ; lorsque TKey est un type de valeur non nullable, cela retournera toujours false. Personnellement, je ne pense pas que je voudrais le faire - les clés nulles représentent presque toujours un bug, d'après mon expérience.

0 votes

Ils le font. Dès que j'ai essayé votre code, les données médiocres avec lesquelles je travaille ont présenté une clé nulle, mais je ne peux pas changer ça maintenant.

32voto

roberocity Points 740

Je favorise les 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; }
  }
}

Cependant, attention. En sous-classant et en utilisant new (car override n'est pas disponible sur le type natif Dictionary), si un objet DictionaryWithDefault est upcasté en un simple Dictionary, l'appel de l'indexeur utilisera l'implémentation de Dictionary de base (en lançant une exception en cas d'absence) plutôt que l'implémentation de la sous-classe.

0 votes

Utilisez trygetvalue de manière plus optimale.

0 votes

Trygetvalue ne vous permet pas de spécifier une valeur par défaut, par exemple pour une valeur de type chaîne vous pouvez vouloir qu'il renvoie "" au lieu de null

0 votes

@nawfal TryGetValue a été utilisé chaque fois que j'ai demandé une valeur dans le dictionnaire. Ce simple wrapper signifie que je peux simplement demander une valeur et il renverra soit la valeur, soit la valeur par défaut que j'ai spécifiée dans le constructeur. Je n'ai pas besoin de me rappeler d'utiliser une méthode spéciale.

26voto

John Sonmez Points 3849

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 : IDictionary {
        private readonly IDictionary dictionary;
        private readonly TValue defaultValue;

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

        public IEnumerator> GetEnumerator() {
            return dictionary.GetEnumerator();
        }

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

        public void Add(KeyValuePair item) {
            dictionary.Add(item);
        }

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

        public bool Contains(KeyValuePair item) {
            return dictionary.Contains(item);
        }

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

        public bool Remove(KeyValuePair 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 Keys {
            get { return dictionary.Keys; }
        }

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

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

Ce projet est un simple décorateur pour un objet IDictionary et une méthode d'extension pour le rendre facile à utiliser.

Le DefaultableDictionary permet de créer un wrapper autour d'un dictionnaire qui fournit une valeur par défaut lors de la tentative d'accès à une clé qui n'existe pas ou en parcourant toutes les valeurs d'un IDictionary.

Exemple: var dictionary = new Dictionary().WithDefaultValue(5);

Article de blog sur l'utilisation également.

0 votes

Veuillez fournir le contexte soutenant le lien Github directement dans votre réponse, sinon il sera probablement supprimé.

4 votes

Ce serait utile si vous ne supprimiez pas les liens qui fournissaient de la clarté également.

8 votes

Je définirais le getter d'index comme suit : public TValue this[TKey key] { get { TValue value; TryGetValue(key, out value); return value; } pour éviter la gestion des exceptions.

6voto

Patrick Karcher Points 11927

Non, rien de tel n'existe. La méthode d'extension est la bonne solution, et votre nom pour cela (GetValueOrDefault) est un assez bon choix.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X