Is there an IDictionary implementation that, on missing key, returns the default value instead of throwing?
如果缺少键,字典中的索引器将引发异常。是否存在将返回默认值(t)的IDictionary实现?
我知道"TryGetValue"方法,但这不可能与Linq一起使用。
这能有效地满足我的需要吗?:
1 | myDict.FirstOrDefault(a => a.Key == someKeyKalue); |
我认为它不会像我认为的那样迭代键,而不是使用哈希查找。
事实上,这样做根本没有效率。
您可以始终编写扩展方法:
1 2 3 4 5 6 7 8 | 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; } |
或使用C 7.1:
1 2 3 | public static TValue GetValueOrDefault<TKey,TValue> (this IDictionary<TKey, TValue> dictionary, TKey key) => dictionary.TryGetValue(key, out var ret) ? ret : default; |
号
它使用:
- 表达体方法(c 6)
- 输出变量(C 7.0)
- 默认文字(C 7.1)
携带这些扩展方法可以帮助……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(); } |
。
如果有人使用.NET Core 2及更高版本(C 7.x),则会引入CollectionExtensions类,如果字典中没有键,则可以使用GetValue或Default方法获取默认值。
当查找缺少的键的值时,
它只适用于它的特殊用途,并且是在泛型之前设计的;如果需要检查整个集合,它没有非常好的枚举器。
这个问题有助于确认
我想提到的一个有趣的C 7特性是Out变量特性,如果您将C 6中的空条件运算符添加到公式中,您的代码可能会更简单,不需要额外的扩展方法。
1 2 3 | var dic = new Dictionary<string, MyClass>(); dic.TryGetValue("Test", out var item); item?.DoSomething(); |
这样做的缺点是你不能像这样做任何事情;
1 | dic.TryGetValue("Test", out var item)?.DoSomething(); |
。
如果我们需要/想要这样做,我们应该编写一个扩展方法,如jon的。
这里有一个版本的@jonsket's for the world of c 7.1,它还允许传入可选的默认值:
1 | public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue; |
如果您想返回
1 2 3 4 5 | 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; } |
。
不幸的是,C还没有?有一个逗号操作符(或C 6提议的分号操作符),所以你必须有一个实际的函数体(gasp!)对于其中一个重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 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(); } } |
。
如果您使用的是ASP.NET MVC,那么就可以利用完成该工作的RouteValueDictionary类。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public object this[string key] { get { object obj; this.TryGetValue(key, out obj); return obj; } set { this._dictionary[key] = value; } } |
可以为字典的键查找函数定义接口。我可能会把它定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 |
具有非泛型键的版本将允许使用非结构键类型的代码来允许任意键差异,这对于泛型类型参数是不可能的。不允许使用可变的
不幸的是,唯一能够实际使用这样一个接口的方法是将
我使用封装来创建一个IDCIETION,这些行为与STL映射非常相似,对于那些熟悉C++的人来说。对于那些没有:
- 在下面的safedictionary中,indexer get返回不存在键的默认值,并使用默认值将该键添加到字典中。这通常是所期望的行为,因为您正在查找最终会出现的项目或有很好的出现机会的项目。
- 方法add(tk key,tv val)表现为addorupdate方法,替换存在的值(如果存在)而不是抛出。我不明白为什么M$没有addorupdate方法,我认为在非常常见的情况下抛出错误是个好主意。
TL/DR-SafeDictionary的编写是为了在任何情况下都不会抛出异常,除了反常的情况,例如计算机内存不足(或起火)。它通过用addorupdate行为替换add并返回默认值,而不是从索引器中抛出notfoundexception来实现这一点。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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(); } } |
使用
1 | var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue; |
。
不需要实现支持默认值的新字典类,只需将查找语句替换为上面的短线即可。
否,因为否则,当键存在但存储了空值时,您如何知道差异?这可能很重要。