是否有一个 IDictionary 实现,在丢失键时返回默认值而不是抛出?

如果键丢失,Dictionary中的索引器将引发异常。是否有一个返回 default(T)IDictionary实现?

我知道 TryGetValue()方法,但是不可能与 LINQ 一起使用。

这能有效地满足我的需要吗:

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

我不认为它会,因为我认为它将迭代键,而不是使用哈希查找。

30080 次浏览

不,因为否则如何知道键存在但存储空值时的差异?这可能意义重大。

实际上,这根本不会有什么效率。

根据注释,在.Net Core 2 +/NetStandard 2.1 +/Net 5,MS 增加了扩展方法 GetValueOrDefault()

对于早期版本,您可以自己编写扩展方法:

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:

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

用途:

  • 一种表达式体方法(C # 6)
  • Out 变量(C # 7.0)
  • 默认值(C # 7.1)

可以为字典的键查找函数定义一个接口,我可能会这样定义它:

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

具有非泛型键的版本将允许使用非结构键类型的代码允许任意键变化,而泛型类型参数则不可能允许这种变化。不应该允许使用一个可变的 Dictionary(Of Cat, String)作为一个可变的 Dictionary(Of Animal, String),因为后者将允许 SomeDictionaryOfCat.Add(FionaTheFish, "Fiona")。但是使用一个可变的 Dictionary(Of Cat, String)作为一个不可变的 Dictionary(Of Animal, String)并没有什么错,因为 SomeDictionaryOfCat.Contains(FionaTheFish)应该被认为是一个完全结构良好的表达式(它应该返回 false,而不必搜索字典,查找任何不属于 Cat类型的内容)。

不幸的是,实际使用这种接口的唯一方法是将 Dictionary对象包装在实现该接口的类中。然而,取决于您正在做的事情,这样的接口和它允许的变化可能会使这种努力变得值得。

携带这些扩展方法可以帮助. 。

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

如果您正在使用 ASP.NET MVC,则可以利用执行此任务的 RouteValueDictionary 类。

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

Collections.Specialized.StringDictionary在查找丢失键的值时提供非异常结果。默认情况下也不区分大小写。

警告

它只对其特殊用途有效,并且在泛型之前设计,如果您需要查看整个集合,那么它没有一个非常好的枚举器。

这个一行程序使用 ContainsKey检查一个键是否存在,然后使用条件运算符返回通常检索到的值或默认值,怎么样?

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

不需要实现支持默认值的新 Dictionary 类,只需将查找语句替换为上面的短行即可。

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

下面是一个针对 C # 7.1世界的@JonSkeet 版本,它也允许传入一个可选的默认值:

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

有两个函数来优化返回 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;
}

不幸的是,C # 还没有(是吗?)有一个逗号运算符(或者 C # 6提出的分号运算符) ,所以你必须有一个实际的函数体(喘气!)超载了。

这个问题有助于确认 TryGetValue在这里扮演 FirstOrDefault的角色。

我想提到的一个有趣的 C # 7特性是 输出变量特性,如果您将 C # 6中的 空条件运算符添加到方程式中,那么您的代码可能会简单得多,而不需要额外的扩展方法。

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

这样做的缺点是你不能像这样做所有的事情;

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

如果我们需要/想要这样做,我们应该编写一个类似 Jon 的扩展方法。

对于那些熟悉 c + + 的人,我使用封装创建了一个行为非常类似于 STL 地图的 IDictionary。对于那些不是的人:

  • 在下面的 SafeDictionary 中,indexer get {}返回默认值(如果没有键) ,则 将该键添加到具有默认值的字典中。这通常是你想要的行为,因为你正在查找最终会出现或者很有可能出现的项目。
  • 方法 Add (TK 键,TV val)作为 AddOrUpdate 方法,替换存在的值,而不是抛出。我不明白为什么 m $没有 AddOrUpdate 方法,并且认为在非常常见的场景中抛出错误是一个好主意。

TL/DR-SafeDictionary 的编写是为了在任何情况下都不会抛出异常,除非出现不正常的场景 ,例如计算机内存不足(或着火)。为此,它将 Add 替换为 AddOrUpdate 行为并返回 default,而不是从索引器引发 NotFoundException。

密码是这样的:

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

如果你吸毒的话。NET Core 2或更高版本(C # 7.x) ,引入了 收藏品扩展类,如果字典中没有键,可以使用 GetValueOrDefault方法获得默认值。

Dictionary<string, string> colorData = new Dictionary<string, string>();
string color = colorData.GetValueOrDefault("colorId", string.Empty);

如果它是 false,那么它可以是检查 TryGetValue并返回默认值的一行程序。

 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

上网试试

如果你吸毒的话。Net Core,可以使用 CollectionExtensions.GetValueOrDefault方法。这与公认答案中提供的实现相同。

public static TValue GetValueOrDefault<TKey,TValue> (
this System.Collections.Generic.IReadOnlyDictionary<TKey,TValue> dictionary,
TKey key);

由于.NET core 2.0,你可以使用:

myDict.GetValueOrDefault(someKeyKalue)

现代答案

从.NET Core 2.0开始,有一个内置的扩展方法,它有两个重载:

TValue GetValueOrDefault<TKey,TValue>(TKey)
TValue GetValueOrDefault<TKey,TValue>(TKey, TValue)

用法:

var dict = new Dictionary<string, int>();


dict.GetValueOrDefault("foo");     // 0: the datatype's default
dict.GetValueOrDefault("foo", 2);  // 2: the specified default

当然,第一个版本为可空类型返回 null

有关更多细节,请参见 文件

一般来说,我会支持来自 Jon Skeet回答,但是我更喜欢一个实现,在这里我可以给出默认值作为参数:

public static TValue GetValueOrDefault<TKey, TValue> (this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
{
if (dictionary.ContainsKey(key))
return dictionary[key];
else
return defaultValue;
}