web-dev-qa-db-fra.com

Existe-t-il une implémentation IDictionary qui, sur la clé manquante, renvoie la valeur par défaut au lieu d'être générée?

L'indexeur dans Dictionnaire lève une exception si la clé est manquante. Existe-t-il une implémentation d'IDictionary qui renverra à la place default (T)?

Je connais la méthode "TryGetValue", mais c'est impossible à utiliser avec linq.

Cela ferait-il efficacement ce dont j'ai besoin ?:

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

Je ne pense pas que ce sera le cas, car je pense qu'il va modifier les clés au lieu d'utiliser une recherche de hachage.

102
TheSoftwareJedi

En effet, cela ne sera pas du tout efficace.

Vous pouvez toujours écrire une méthode d'extension:

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

Ou avec C # 7.1:

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

Cela utilise:

  • Une méthode basée sur l'expression (C # 6)
  • Une variable out (C # 7.0)
  • Un littéral par défaut (C # 7.1)
119
Jon Skeet

Porter ces méthodes d'extension peut aider.

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key)
{
    return dict.GetValueOrDefault(key, default(V));
}

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, V defVal)
{
    return dict.GetValueOrDefault(key, () => defVal);
}

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

Collections.Specialized.StringDictionary fournit un résultat non-exception lors de la recherche de la valeur d'une clé manquante. Il est également insensible à la casse par défaut.

Mises en garde

Il n’est valable que pour ses utilisations spécialisées et, étant conçu avant les génériques, il n’a pas un très bon énumérateur si vous avez besoin de revoir toute la collection.

3
Mark Hurd

Si quelqu'un utilisant .net core 2 et supérieur (C # 7.X), CollectionExtensions class est introduit et peut utiliser GetValueOrDefault méthode pour obtenir la valeur par défaut si key n'est pas dans le dictionnaire. 

1
cdev

On pourrait définir une interface pour la fonction de recherche de clé d'un dictionnaire. Je le définirais probablement comme quelque chose comme:

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 version avec des clés non génériques permettrait au code qui utilisait du code utilisant des types de clé non structurés de permettre une variance de clé arbitraire, ce qui ne serait pas possible avec un paramètre de type générique. On ne devrait pas être autorisé à utiliser une Dictionary(Of Cat, String) mutable en tant que Dictionary(Of Animal, String) mutable puisque ce dernier permettrait SomeDictionaryOfCat.Add(FionaTheFish, "Fiona"). Mais il n'y a rien de mal à utiliser une Dictionary(Of Cat, String) mutable__ en tant que Dictionary(Of Animal, String) immuable, puisque SomeDictionaryOfCat.Contains(FionaTheFish) devrait être considéré comme une expression parfaitement formée (elle devrait retourner false, sans avoir à chercher dans le dictionnaire, pour tout ce qui ne serait pas de type Cat).

Malheureusement, la seule façon d’utiliser une telle interface est d’envelopper un objet Dictionary dans une classe qui implémente l’interface. En fonction de ce que vous faites, cependant, une telle interface et la variance qu’elle permet pourraient en valoir la peine.

1
supercat

Cette question a permis de confirmer que la variable TryGetValue joue ici le rôle FirstOrDefault.

Une fonctionnalité intéressante de C # 7 que je voudrais mentionner est la fonctionnalité out variables , et si vous ajoutez l'opérateur null-conditionnel de C # 6 à l'équation, votre code pourrait être beaucoup plus simple sans avoir besoin de méthodes d'extension supplémentaires.

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

L'inconvénient est que vous ne pouvez pas tout faire en ligne de la sorte.

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

Si nous avions besoin de faire cela, nous devrions coder une méthode d’extension comme celle de Jon.

1
hardkoded
public class DefaultIndexerDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private IDictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();

    public TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val))
                return default(TValue);
            return val;
        }

        set { _dict[key] = value; }
    }

    public ICollection<TKey> Keys => _dict.Keys;

    public ICollection<TValue> 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<TKey, TValue> item)
    {
        _dict.Add(item);
    }

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

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

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

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

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

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

    public bool Remove(KeyValuePair<TKey, TValue> 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();
    }
}
1
Brian

Voici une version de @ JonSkeet pour le monde de C # 7.1 qui permet également de transmettre une valeur par défaut facultative:

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

Il peut être plus efficace d’avoir deux fonctions pour optimiser le cas où vous souhaitez renvoyer default(TV):

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

Malheureusement, C # n’a pas (encore?) D’opérateur par virgule (ou d’opérateur proposé par un point-virgule C # 6).

1
NetMage

Si vous utilisez ASP.NET MVC, vous pouvez exploiter la classe RouteValueDictionary qui effectue le travail.

public object this[string key]
{
  get
  {
    object obj;
    this.TryGetValue(key, out obj);
    return obj;
  }
  set
  {
    this._dictionary[key] = value;
  }
}
1
Jone Polvora

Il pourrait s'agir d'une ligne pour vérifier TryGetValue et renvoyer la valeur par défaut s'il s'agit de false.

 Dictionary<string, int> myDic = new Dictionary<string, int>() { { "One", 1 }, { "Four", 4} };
 string myKey = "One"
 int value = myDic.TryGetValue(myKey, out value) ? value : 100;

myKey = "One" => value = 1

myKey = "two" => value = 100

myKey = "Four" => value = 4

Essayez-le en ligne

0
Aryan Firouzian

J'ai utilisé l'encapsulation pour créer un IDictionary avec un comportement très similaire à un carte STL, pour ceux d'entre vous qui sont familiers avec c ++. Pour ceux qui ne le sont pas:

  • indexer get {} dans SafeDictionary ci-dessous renvoie la valeur par défaut si une clé n'est pas présente, et ajoute cette clé au dictionnaire avec une valeur par défaut. C'est souvent le comportement souhaité, car vous recherchez des éléments qui apparaîtront ou auront de bonnes chances d'apparaître.
  • method Add (clé TK, TV val) se comporte comme une méthode AddOrUpdate, remplaçant la valeur présente si elle existe au lieu d'être renvoyée. Je ne vois pas pourquoi m $ n'a pas de méthode AddOrUpdate et pense que le fait de jeter des erreurs dans des scénarios très courants est une bonne idée.

TL/DR - SafeDictionary est écrit de manière à ne jamais lancer d'exceptions sous aucune circonstance autre que des scénarios pervers, telle que l'ordinateur manque de mémoire (ou en feu). Pour ce faire, il remplace le comportement Add par AddOrUpdate et renvoie la valeur par défaut au lieu de lancer NotFoundException à partir de l'indexeur.

Voici le code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class SafeDictionary<TK, TD>: IDictionary<TK, TD> {
    Dictionary<TK, TD> _underlying = new Dictionary<TK, TD>();
    public ICollection<TK> Keys => _underlying.Keys;
    public ICollection<TD> 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<TK, TD>[] 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<TK, TD> item) {
        _underlying[item.Key] = item.Value;
    }

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

    public bool Contains(KeyValuePair<TK, TD> item) {
        return _underlying.Contains(item);
    }

    public bool ContainsKey(TK key) {
        return _underlying.ContainsKey(key);
    }

    public IEnumerator<KeyValuePair<TK, TD>> GetEnumerator() {
        return _underlying.GetEnumerator();
    }

    public bool Remove(TK key) {
        return _underlying.Remove(key);
    }

    public bool Remove(KeyValuePair<TK, TD> 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();
    }
}
0
neural

Qu'en est-il de cette ligne unique qui vérifie la présence d'une clé à l'aide de ContainsKey, puis renvoie la valeur normalement renvoyée ou la valeur par défaut à l'aide de l'opérateur conditionnel?

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

Pas besoin d'implémenter une nouvelle classe Dictionary qui supporte les valeurs par défaut, remplacez simplement vos instructions de recherche par la ligne courte ci-dessus.

0
Byte Commander