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

时间:2009-02-11 20:54:33

标签: c# .net hash dictionary

如果缺少密钥,则Index into Dictionary会引发异常。是否有IDictionary的实现,而是返回默认值(T)?

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

这会有效地做我需要的吗?:

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

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

16 个答案:

答案 0 :(得分:127)

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

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

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)

答案 1 :(得分:17)

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

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

答案 2 :(得分:5)

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

答案 3 :(得分:3)

查找缺失键的值时,

Collections.Specialized.StringDictionary会提供非异常结果。它默认情况下也不区分大小写。

注意事项

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

答案 4 :(得分:2)

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

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

答案 5 :(得分: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对象包装在实现接口的类中。然而,根据您正在做的事情,这样的界面及其允许的差异可能使其值得付出努力。

答案 6 :(得分: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;
  }
}

答案 7 :(得分:1)

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

答案 8 :(得分: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提出的分号运算符),所以你必须有一个实际的函数体(喘气!)用于其中一个重载。

答案 9 :(得分:1)

此问题有助于确认TryGetValue在此处扮演FirstOrDefault角色。

我想提到的一个有趣的C#7功能是out variables功能,如果您将C#6中的null-conditional operator添加到等式中,您的代码可以更简单,无需额外的扩展方法。

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

这样做的缺点是你不能像这样做内联;

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

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

答案 10 :(得分:1)

自.NET core 2.0起,您可以使用:

myDict.GetValueOrDefault(someKeyKalue)

答案 11 :(得分:0)

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

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

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

答案 12 :(得分: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

Try it online

答案 13 :(得分:-1)

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

答案 14 :(得分:-1)

使用ContainsKey检查密钥是否存在然后使用条件运算符返回正常检索值或默认值的单行怎么办?

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

无需实现支持默认值的新Dictionary类,只需用上面的短行替换查找语句。

答案 15 :(得分:-1)

在 .NET 5 核心中,GetValueOrDefault 扩展是开箱即用的。

public static TValue? GetValueOrDefault<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key);