是否有一个IDictionary实现,在缺少键的情况下返回默认值而不是抛出该默认值?


129

如果缺少键,则字典中的索引器将引发异常。是否有IDictionary的实现将返回default(T)的实现?

我知道“ TryGetValue”方法,但是不能与linq一起使用。

这会有效地满足我的需求吗:

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

我认为它不会像我认为的那样会迭代键,而不是使用哈希查找。


10
另请参阅后面的问题,该问题被标记为与此问题的重复,但是答案有所不同:如果密钥不存在,则字典返回默认值
Rory O'Kane 2013年

2
@dylan这将导致缺少键的异常,而不是null。另外,这将需要确定在使用字典的任何地方都应使用默认值。还要注意这个问题的年龄。我们没有?? 运营商8年前无论如何
TheSoftwareJedi

Answers:


147

确实,那根本不会有效。

您总是可以编写扩展方法:

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)
  • 输出变量(C#7.0)
  • 默认文字(C#7.1)

2
或更紧凑:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck 2012年

33
@PeterGluck:更紧凑,但是效率更低...为什么在有钥匙的情况下进行两次查找?
乔恩·斯基特

5
@JonSkeet:感谢您纠正彼得;我一直在使用这种“效率低下”的方法,但有一段时间没有意识到。
J Bryan Price

5
非常好!我要无耻地窃-嗯,把它叉起来。;)
乔恩·库姆斯

3
显然,MS认为这足以添加到System.Collections.Generic.CollectionExtensions,就像我刚刚尝试过的那样,它已经存在。
–theMayer

19

进行这些扩展方法会有所帮助。

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

3
最后一个重载很有趣。由于没有什么选择,这是因为懒惰评价的一种形式?
MEMark 2014年

1
@MEMark yes值选择器仅在需要时才运行,因此在某种程度上可以说是惰性计算,但不像Linq上下文中那样延迟执行。但是这里的惰性评估并不依赖于任何因素,您当然可以使选择器成为可能Func<K, V>
2014年

1
第三种方法是否公开,因为它本身就有用?也就是说,您是否认为某人可能会传入使用当前时间的lambda值,或者是基于...的计算。我本来希望第二种方法做V值。返回dict.TryGetValue(key,out value)?值:defVal;
乔恩·库姆斯

1
@JCoombs对,第三个重载也出于相同的目的。当然,您可以在第二次重载中执行此操作,但是我让它有点(这样我就不重复逻辑了)。
nawfal 2014年

4
@nawfal好点。保持干燥比避免一个简单的方法调用更为重要。
乔恩·库姆斯

19

如果有人使用.net core 2及更高版本(C#7.X),则会引入CollectionExtensions类,并且如果字典中没有键,则可以使用GetValueOrDefault方法获取默认值。

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

4

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

注意事项

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


4

如果使用的是.Net Core,则可以使用CollectionExtensions.GetValueOrDefault方法。这与接受的答案中提供的实现相同。

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

3
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

可以为字典的键查找功能定义一个接口。我可能将其定义为:

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")。但是将mutable Dictionary(Of Cat, String)用作不可变对象并没有错Dictionary(Of Animal, String),因为SomeDictionaryOfCat.Contains(FionaTheFish)应该将其视为格式正确的表达式(它应该返回false,而不必搜索字典中所有非类型的东西Cat)。

不幸的是,唯一能够实际使用这种接口的方法是,如果将Dictionary对象包装在实现该接口的类中。但是,取决于您在做什么,这样的接口及其允许的差异可能值得您付出努力。


1

如果您使用的是ASP.NET MVC,则可以利用RouteValueDictionary类来完成这项工作。

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

1

这个问题有助于确认TryGetValue剧本FirstOrDefault这里角色。

我要提到的一个有趣的C#7功能是out变量功能,如果将C#6中的空条件运算符添加到方程中,则代码可能会更加简单,而无需其他扩展方法。

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

这样做的缺点是您不能像这样进行内联的所有操作;

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

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


1

这是适用于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建议的分号运算符),因此对于其中一个重载,您必须具有实际的函数体(gasp!)。


1

我使用封装来创建一个IDictionary,其行为与STL映射非常相似,以供熟悉c ++的人员使用。对于那些不是的人:

  • 如果不存在键,则下面的SafeDictionary中的indexer get {}返回默认值,并且补充说,关键有一个默认值的字典。这通常是理想的行为,因为您正在查找最终会出现或很有可能出现的项目。
  • 方法Add(TK key,TV val)充当AddOrUpdate方法,替换存在的值(如果存在)而不是抛出。我不明白为什么m $没有AddOrUpdate方法,并且认为在非常常见的情况下抛出错误是个好主意。

TL / DR-编写SafeDictionary的目的是,在不正常的情况下(例如计算机内存不足(或着火)),在任何情况下都不会引发异常。它通过用AddOrUpdate行为替换Add并返回默认值而不是从索引器中抛出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();
    }
}

1

从.NET Core 2.0开始,您可以使用:

myDict.GetValueOrDefault(someKeyKalue)

0

那么这种单行代码会如何检查是否存在键ContainsKey,然后使用条件运算符返回正常检索的值或默认值呢?

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

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


5
它具有两个查找而不是一个查找,并且如果您使用的是ConcurrentDictionary,则会引入竞争条件。
piedar

0

如果为,则可以检查TryGetValue并返回默认值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

在线尝试


-1

不可以,因为否则,当键存在但存储了空值时,您如何知道差异?那可能很重要。


11
可能是-但在某些情况下,您可能会知道并非如此。我可以看到有时候这很有用。
乔恩·斯基特

8
如果您知道null不在其中-那就太好了。它使加入Linq中的这些事情(这是我最近所做的一切)变得非常容易
TheSoftwareJedi

1
通过使用ContainsKey方法?
MattDavey 2011年

4
是的,它实际上非常有用。Python具有此功能,当我碰巧使用Python时,我使用它的方式远远超过普通方法。节省大量额外的if语句,这些语句仅检查空结果。(您是对的,当然,空值偶尔会有用,但是通常我想要的是“”或0。)
Jon Coombs 2014年

我对此表示赞同。但是如果今天是我不会的。现在不能取消:)。我赞成这些评论以表达自己的观点:)
nawfal 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.