web-dev-qa-db-fra.com

Comment obtenir null à la place de KeyNotFoundException en accédant à la valeur du dictionnaire par clé?

Dans certains cas, il m'a semblé utile de disposer d'un moyen lisible et bref pour obtenir null au lieu de KeyNotFoundException lors de l'accès à la valeur du dictionnaire par clé, lorsqu'il n'existe pas de clé de ce type. dans le dictionnaire.

La première chose qui me vint à l’esprit fut une méthode d’extension:

public static U GetValueByKeyOrNull<T, U>(this Dictionary<T, U> dict, T key)
        where U : class //it's acceptable for me to have this constraint
{
    if (dict.ContainsKey(key))
        return dict[key];
    else 
        //it could be default(U) to use without U class constraint
        //however, I didn't need this.
        return null; 
}

Mais ce n'est pas très court en fait, quand vous écrivez quelque chose comme ceci:

string.Format("{0}:{1};{2}:{3}",                                                 
              dict.GetValueByKeyOrNull("key1"),
              dict.GetValueByKeyOrNull("key2"),
              dict.GetValueByKeyOrNull("key3"),
              dict.GetValueByKeyOrNull("key4"));

Je dirais qu'il serait beaucoup mieux d'avoir quelque chose de proche de la syntaxe de base: dict["key4"].

Ensuite, j'ai eu l'idée de créer une classe avec un champ de dictionnaire private, qui exposerait les fonctionnalités dont j'avais besoin:

public class MyDictionary<T, U> //here I may add any of interfaces, implemented
                                //by dictionary itself to get an opportunity to,
                                //say, use foreach, etc. and implement them
                                // using the dictionary field.
        where U : class
{
    private Dictionary<T, U> dict;

    public MyDictionary()
    {
        dict = new Dictionary<T, U>();
    }

    public U this[T key]
    {
        get
        {
            if (dict.ContainsKey(key))
                return dict[key];
            else
                return null;
        }
        set
        {
            dict[key] = value;
        }
    }
}

Mais il semble un peu trop coûteux d’obtenir un léger changement dans le comportement de base.

Une autre solution pourrait être de définir un Func dans le contexte actuel, comme ceci:

Func<string, string> GetDictValueByKeyOrNull = (key) =>
{
    if (dict.ContainsKey(key))
        return dict[key];
    else
        return null;
};

de sorte qu'il pourrait être utilisé comme GetDictValueByKeyOrNull("key1").

Pourriez-vous, s'il vous plaît, me donner d'autres suggestions ou de l'aide pour en choisir une meilleure?

66
horgh

En fin de compte, j'ai proposé une variante utilisant une classe de dictionnaire dérivée avec une implémentation d'interface explicite:

public interface INullValueDictionary<T, U>
    where U : class
{
    U this[T key] { get; }
}

public class NullValueDictionary<T, U> : Dictionary<T, U>, INullValueDictionary<T, U>
    where U : class
{
    U INullValueDictionary<T, U>.this[T key]
    {
        get
        {
            U val;
            dict.TryGet(key, out val);
            return val;
        }
    }
}

Donc, il expose les fonctionnalités dont j'ai besoin de la manière suivante:

//create some dictionary
NullValueDictionary<int, string> dict = new NullValueDictionary<int, string>
{
    {1,"one"}
};
//have a reference to the interface
INullValueDictionary<int, string> idict = dict;

try
{
    //this throws an exception, as the base class implementation is utilized
    Console.WriteLine(dict[2] ?? "null");
}
catch { }
//this prints null, as the explicit interface implementation 
//in the derived class is used
Console.WriteLine(idict[2] ?? "null");
22
horgh

Voici ma solution de ma bibliothèque personnelle, implémentée en tant que méthode d'extension. Je ne la poste que parce qu'elle est mise en œuvre à partir de l'interface du dictionnaire et permet de transmettre une valeur par défaut facultative.

Mise en oeuvre

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

tilisation

 MyDictionary.GetValue("key1");
 MyDictionary.GetValue("key2", -1);
 MyDictionary.GetValue("key3")?.SomeMethod();
51
Jared G

Vous ne pouvez pas obtenir la syntaxe souhaitée avec une méthode d'extension, et comme d'autres l'ont déjà dit, remplacer une méthode/un opérateur pour en modifier le comportement n'est généralement pas une bonne idée. Je pense que le mieux que vous puissiez faire est de raccourcir le nom que vous utilisez.

C'est si vous devez rester à l'interface IDictionary. Si vous n'interférez pas avec du code qui attend un IDictionary, vous êtes libre de définir votre propre interface et laisser l'opérateur [] travailler différemment ne pose pas de problème.

Quoi que vous appeliez la fonction, vous voudrez l'implémenter comme ceci:

public static U Get<T, U>(this Dictionary<T, U> dict, T key)
    where U : class
{
    U val;
    dict.TryGetValue(key, out val);
    return val;
}

Il ne fait qu'une recherche, comparé à 2 pour vos implémentations.

31
bmm6o

Ajouter une classe DictionaryExtension

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

Et il peut retourner valeur par défaut si la clé n’est pas trouvée dans le dictionnaire.

La valeur par défaut est null s'il s'agit d'un type de référence.

_dic.GetValueOrDefault();
10
TimChang

Je postule en disant que je voudrais pas utiliser ceci. Le mot-clé new, bien que utile dans ce cas, peut créer des bogues très difficiles à trouver. Autre que cela, vous pouvez essayer cette classe.

class MyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public new TValue this[TKey key]
    {
        get
        {
            TValue value;
            return TryGetValue(key, out value) ? value : default(TValue);
        }
        set { base[key] = value; }
    }
}
5
Mir

Cela vaut la peine de souligner que HybridDictionary le fait par défaut. Vous perdez la saisie générique, mais vous obtenez la fonctionnalité null si introuvable.

Et (du moins théoriquement), vous obtenez des avantages en termes de performances pour un nombre de valeurs faible, je pense.

2
Brondahl

Je l'ai déjà fait auparavant et cela a plutôt bien fonctionné d'hériter simplement de la classe Dictionary et de masquer l'indexeur. C'est vraiment simple de le faire de cette façon afin que vous acquériez automatiquement toute la fiabilité et la familiarité de la classe Dictionnaire Régulier.

public class NullSafeDict<TKey, TValue> : Dictionary<TKey, TValue> where TValue : class
{
    public new TValue this[TKey key]
    {
        get
        {
            if (!ContainsKey(key))
                return null;
            else
                return base[key];
        }
        set
        {
            if (!ContainsKey(key))
                Add(key, value);
            else
                base[key] = value;
        }
    }
}
0
Mike Socha III