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