Is there an IDictionary implementation that, on missing key, returns the default value instead of throwing?

前端 未结 15 1752
暗喜
暗喜 2020-11-27 04:00

The indexer into Dictionary throws an exception if the key is missing. Is there an implementation of IDictionary that instead will return de

相关标签:
15条回答
  • 2020-11-27 04:39

    I used encapsulation to create an IDictionary with behavior very similar to an STL map, for those of you who are familiar with c++. For those who aren't:

    • indexer get {} in SafeDictionary below returns the default value if a key is not present, and adds that key to the dictionary with a default value. This is often the desired behavior, as you're looking up items that will appear eventually or have a good chance of appearing.
    • method Add(TK key, TV val) behaves as an AddOrUpdate method, replacing the value present if it exists instead of throwing. I don't see why m$ doesn't have an AddOrUpdate method and thinks throwing errors in very common scenarios is a good idea.

    TL/DR - SafeDictionary is written so as to never throw exceptions under any circumstances, other than perverse scenarios, such as the computer being out of memory (or on fire). It does this by replacing Add with AddOrUpdate behavior and returning default instead of throwing NotFoundException from the indexer.

    Here's the code:

    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-27 04:40

    Here is a version of @JonSkeet's for the world of C# 7.1 that also allows for an optional default to be passed in:

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

    It may be more efficient to have two functions to optimize the case where you want to return 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;
    }
    

    Unfortunately C# doesn't (yet?) have a comma operator (or the C# 6 proposed semicolon operator) so you have to have an actual function body (gasp!) for one of the overloads.

    0 讨论(0)
  • 2020-11-27 04:41

    If someone using .net core 2 and above (C# 7.X), CollectionExtensions class is introduced and can use GetValueOrDefault method to get default value if key is not there in a dictionary.

    Dictionary<string, string> colorData = new  Dictionary<string, string>();
    string color = colorData.GetValueOrDefault("colorId", string.Empty);
    
    0 讨论(0)
  • 2020-11-27 04:41

    No, because otherwise how would you know the difference when the key exists but stored a null value? That could be significant.

    0 讨论(0)
  • 2020-11-27 04:42
    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();
        }
    }
    
    0 讨论(0)
  • 2020-11-27 04:43

    If you are using ASP.NET MVC, you could leverage the RouteValueDictionary class that do the job.

    public object this[string key]
    {
      get
      {
        object obj;
        this.TryGetValue(key, out obj);
        return obj;
      }
      set
      {
        this._dictionary[key] = value;
      }
    }
    
    0 讨论(0)
提交回复
热议问题