【问题标题】:Is there an IDictionary implementation that, on missing key, returns the default value instead of throwing?是否有一个 IDictionary 实现,在缺少键时,返回默认值而不是抛出?
【发布时间】:2010-10-07 00:38:46
【问题描述】:

如果缺少键,Dictionary 的索引器会引发异常。是否有IDictionary 的实现会返回default(T)

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

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

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

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

【问题讨论】:

  • 另请参阅后面的问题,标记为与此问题重复,但答案不同:Dictionary returning a default value if the key does not exist
  • @dylan 这将在缺少键时引发异常,而不是 null。此外,还需要确定使用字典的任何地方的默认值。还要注意这个问题的年龄。我们没有??无论如何 8 年前的运营商

标签: c# .net hash dictionary


【解决方案1】:

确实,这根本没有效率。

根据 cmets,在 .Net Core 2+ / NetStandard 2.1+ / Net 5 中,MS added the extension method 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)
  • 输出变量 (C# 7.0)
  • 默认文字 (C# 7.1)

【讨论】:

  • 或更简洁:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
  • @PeterGluck:更紧凑,但效率更低......为什么在密钥存在的情况下执行两次查找?
  • @JonSkeet:感谢纠正彼得;我一直在使用这种“效率较低”的方法,但现在没有意识到。
  • 非常好!我要无耻地抄袭——呃,叉它。 ;)
  • 显然 MS 认为这足以添加到 System.Collections.Generic.CollectionExtensions,因为我刚刚尝试过它就在那里。
【解决方案2】:

如果您使用的是 .NET Core 2 或更高版本 (C# 7.x),则引入了 CollectionExtensions 类,如果字典中没有键,您可以使用 GetValueOrDefault 方法获取默认值。

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

【讨论】:

    【解决方案3】:

    携带这些扩展方法会有所帮助..

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

    【讨论】:

    • 最后一个重载很有趣。既然没有什么可以选择from,这仅仅是一种惰性求值的形式吗?
    • @MEMark 是值选择器仅在需要时运行,因此在某种程度上它可以说是惰性评估,但它不像 Linq 上下文中那样延迟执行。但是这里的惰性求值并不依赖于nothing to select from因素,你当然可以将选择器设为Func&lt;K, V&gt;
    • 第三种方法是公开的,因为它本身就很有用吗?也就是说,您是否认为有人可能会传入一个使用当前时间的 lambda,或者基于...的计算。我本来希望只有第二种方法做 V 值;返回 dict.TryGetValue(key, out value) ?值:defVal;
    • @JCoombs 对,第三个重载是出于同样的目的。当然,你可以在第二次重载时做到这一点,但我让它有点(这样我就不重复逻辑了)。
    • @nawfal 好点。保持干爽比避免一个微不足道的方法调用更重要。
    【解决方案4】:

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

    注意事项

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:
        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();
            }
        }
        

        【讨论】:

        • 所以我们可以这样使用:myDict[key]?.Name??"" ?使用示例会很棒
        【解决方案7】:

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

        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,而无需搜索字典来查找任何不可变的内容) 't 类型为 Cat)。

        不幸的是,能够实际使用这种接口的唯一方法是在实现该接口的类中包装Dictionary 对象。但是,根据您正在做的事情,这样的界面及其允许的变化可能值得付出努力。

        【讨论】:

          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

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

            • 如果键不存在,则下面 SafeDictionary 中的 indexer get {} 返回默认值,并且将该键添加到具有默认值的字典中。这通常是所需的行为,因为您正在查找最终会出现或很有可能出现的项目。
            • 方法 Add(TK key, TV val) 的行为类似于 AddOrUpdate 方法,如果存在的值存在则替换它而不是抛出。我不明白为什么 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();
                }
            }
            

            【讨论】:

              【解决方案10】:

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

              myDict.GetValueOrDefault(someKeyKalue)
              

              【讨论】:

                【解决方案11】:

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

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

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

                【讨论】:

                • 这有两个查找而不是一个,如果您使用 ConcurrentDictionary,它会引入竞争条件。
                【解决方案12】:

                这个问题有助于确认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 的扩展方法。

                【讨论】:

                  【解决方案13】:

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

                  【讨论】:

                    【解决方案14】:

                    检查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

                    【讨论】:

                      【解决方案15】:

                      现代答案

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

                      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

                      更多详情请参阅documentation

                      【讨论】:

                        【解决方案16】:

                        一般来说,我会支持来自Jon Skeetanswer,但是我更喜欢可以将默认值作为参数的实现:

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

                        【讨论】:

                          【解决方案17】:

                          不,否则当键存在但存储空值时,您怎么知道区别?这可能很重要。

                          【讨论】:

                          • 它可能是 - 但在某些情况下你可能很清楚它不是。我可以看到这有时很有用。
                          • 如果你知道 null 不在那里 - 这是非常好的。它使得在 Linq 中加入这些东西(这是我最近所做的)变得非常容易
                          • 使用 ContainsKey 方法?
                          • 是的,它实际上非常有用。 Python 有这个,当我碰巧在 Python 中时,我使用它的次数远远超过普通方法。节省了编写大量仅检查空结果的额外 if 语句。 (当然,你是对的,null 偶尔有用,但通常我想要一个 "" 或 0。)
                          • 我对此表示赞同。但如果是今天,我就不会了。现在不能取消:)。我赞成 cmets 来表达我的观点 :)
                          【解决方案18】:

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

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

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2011-06-17
                            • 1970-01-01
                            • 1970-01-01
                            • 2021-11-16
                            • 1970-01-01
                            • 2019-06-22
                            • 2020-06-13
                            • 1970-01-01
                            相关资源
                            最近更新 更多