265

I find myself using the current pattern quite often in my code nowadays

var dictionary = new Dictionary<type, IList<othertype>>();
// Add stuff to dictionary

var somethingElse = dictionary.ContainsKey(key) ? dictionary[key] : new List<othertype>();
// Do work with the somethingelse variable

Or sometimes

var dictionary = new Dictionary<type, IList<othertype>>();
// Add stuff to dictionary

IList<othertype> somethingElse;
if(!dictionary.TryGetValue(key, out somethingElse) {
    somethingElse = new List<othertype>();
}

Both of these ways feel quite roundabout. What I really would like is something like

dictionary.GetValueOrDefault(key)

Now, I could write an extension method for the dictionary class that does this for me, but I figured that I might be missing something that already exists. SO, is there a way to do this in a way that is more "easy on the eyes" without writing an extension method to dictionary?

wasatz
  • 4,158
  • 2
  • 23
  • 30
  • Not sure why all the below answers are so complex. Just use the coalescing operator: `string valFromDict = someDict["someKey"] ?? "someDefaultVal";` – dylanh724 Oct 23 '17 at 13:59
  • 1
    @DylanHunt That doesnt work for value types though. ;) – wasatz Oct 24 '17 at 05:52
  • Also note that using the `[]` operator also has the possibly unwanted side effect of adding a value to the dictionary if one doesn't exist. – Jonathan Lidbeck Mar 04 '19 at 17:28
  • 2
    Since this is still the top question when googling this, really key for folks to check out the linked answer. This feature has [existed for years](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.collectionextensions.getvalueordefault?view=net-7.0#system-collections-generic-collectionextensions-getvalueordefault-2(system-collections-generic-ireadonlydictionary((-0-1))-0-1)) – data princess Jan 10 '23 at 17:45
  • @dataprincess GetValueOrDefault() could be nice to just say what it is instead of an external link btw this is what exactly the OP asked. TryGetValue in accepted answer is not a one liner, it just returns boolean, until it was edited for the comments though – Alireza Jamali Jun 12 '23 at 08:56

4 Answers4

364

As per comments, in .Net Core 2+ / NetStandard 2.1+ / Net 5, MS added the extension method GetValueOrDefault()


TryGetValue will already assign the default value for the type to the dictionary, so you can just use:

dictionary.TryGetValue(key, out value);

and just ignore the return value. However, that really will just return default(TValue), not some custom default value (nor, more usefully, the result of executing a delegate). There's nothing more powerful built into the framework. I would suggest two extension methods:

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    TValue defaultValue)
{
    return dictionary.TryGetValue(key, out var value) ? value : defaultValue;
}

public static TValue GetValueOrDefault<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    Func<TValue> defaultValueProvider)
{
    return dictionary.TryGetValue(key, out var value) ? value : defaultValueProvider();
}

(You may want to put argument checking in, of course :)

freedomn-m
  • 27,664
  • 8
  • 35
  • 57
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    To open a can of worms, these extensions work when key doesn't exist, but TryGetValue throws if key is null. Knowing whether key is nullable or not would make this a trivial check. – ProfK Sep 27 '12 at 06:12
  • 5
    @ProfK: You can just compare the key with `null` anyway; when TKey is a non-nullable value type, it will just always return false. Personally I don't think I'd want to do that though - null keys *almost* always represent a bug, in my experience. – Jon Skeet Sep 27 '12 at 06:17
  • They do. Just as soon as I tried your code, the crappy data I'm dealing with presented a null key, but I can't change that now. – ProfK Sep 27 '12 at 10:18
  • 3
    @ProfK: Right - it makes sense to change the code for your particular case then, but I wouldn't want to propose it in general :) – Jon Skeet Sep 27 '12 at 10:29
  • 17
    Thanks for this. How about a default value for 3rd arg? TValue defaultValue = default(TValue) – crokusek Nov 09 '12 at 02:55
  • This extension method by the way also enables using TryGetValue against a dictionary (created by LINQ) where the value type is an anonymous type. – springy76 Mar 10 '17 at 11:11
  • 4
    Given Jon's answer I was stuck on how to actually call his extension methods (having never done that before) and so I stumbled across this MS article: https://msdn.microsoft.com/en-us/library/bb311042.aspx. – err1 Mar 22 '17 at 11:51
  • 7
    In later versions you can use System.Collections.Generic which provides GetValueOrDefault(key, defaultValue). – Patrik Meijer Feb 27 '20 at 17:41
32

I do favor extension methods, but here's a simple class I use from time to time to handle dictionaries when I need default values.

I wish this were just part of the base Dictionary class.

public class DictionaryWithDefault<TKey, TValue> : Dictionary<TKey, TValue>
{
  TValue _default;
  public TValue DefaultValue {
    get { return _default; }
    set { _default = value; }
  }
  public DictionaryWithDefault() : base() { }
  public DictionaryWithDefault(TValue defaultValue) : base() {
    _default = defaultValue;
  }
  public new TValue this[TKey key]
  {
    get { 
      TValue t;
      return base.TryGetValue(key, out t) ? t : _default;
    }
    set { base[key] = value; }
  }
}

Beware, however. By subclassing and using new (since override is not available on the native Dictionary type), if a DictionaryWithDefault object is upcast to a plain Dictionary, calling the indexer will use the base Dictionary implementation (throwing an exception if missing) rather than the subclass's implementation.

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Rob Sutherland
  • 1,211
  • 12
  • 14
  • Use trygetvalue better.. – nawfal Mar 30 '13 at 21:18
  • trygetvalue doesn't allow you to specify a default value, ie for a string value you may want it to return "" instead of null – katbyte Aug 12 '13 at 20:08
  • @nawfal TryGetValue has be used every time I ask for a value in the dictionary. This simple wrapper means that I can just ask for a value and it will either return the value or the default I specified in the constructor. I don't have to remember to use a special method. – Rob Sutherland Sep 19 '13 at 20:21
  • @roberocity No, I meant use `TryGetValue` inside your indexer (instead of `base.ContainsKey(key) ? base[key]`) – nawfal Sep 20 '13 at 04:35
  • @Katbyte see my comment above. – nawfal Sep 20 '13 at 04:35
  • @nawfal. Ahh. Ok. Is that a personal preference or is there a bit of a performance boost? – Rob Sutherland Sep 20 '13 at 11:29
  • 1
    @roberocity yes a performance advantage, see this: http://stackoverflow.com/questions/9382681/what-is-more-efficient-dictionary-trygetvalue-or-containskeyitem – nawfal Sep 20 '13 at 11:36
  • 1
    There is a bug in the above code. It will always return 0 as default value, because TryGetValue sets "t" to 0. Change to; TValue t; if (!TryGetValue(key, out t)) { t = m_default; } return t; – Manuel Amstutz Oct 31 '13 at 13:59
  • 9
    I think it is ill-advised to inherit from `Dictionary` in this case: by using the `new` keyword you are hiding the non-virtual `Item` property. If someone was to access it from a `Dictionary` reference type, and not a `DictionaryWithDefault`, it is `Dictionary`'s `Item` property that would be called, not yours. – bavaza Feb 06 '14 at 09:07
  • The ctor that uses `TValue defaultValue` seems of little value. The only time I can see using a specific value instead of `default(TValue)` is when TValue is a string, and you want the default to be `String.Empty`. In far more cases, you'd want the default to be `new TValue()` --- but you can't pass that to the ctor as it would assign the same object to each new entry. My idea would be a ctor taking a bool, (which defaults to false). if true, `new TValue()`. Unfortunately, that would require a `with TValue : new()` qualifier, which would preclude using string as a TValue. – James Curran Oct 06 '16 at 22:02
  • Instead of subclassing `Dictionary<,>` it's possible to have an underlying `Dictionary<,>` - or even better `IDictionary<,>` - and delegate everything to it (with the sole exception of a custom getter for `this[]` that handles returning the default value in case the underlying dictionary doesn't contain the key). It's more code, but cleaner and more flexible. – Tom Oct 04 '19 at 22:15
  • If you want to implement it by yourself you can create a simple Extension Method instead of building your own class – nrofis Jun 18 '20 at 15:39
26

I created a DefaultableDictionary to do exactly what you are asking for!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace DefaultableDictionary {
    public class DefaultableDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
        private readonly IDictionary<TKey, TValue> dictionary;
        private readonly TValue defaultValue;

        public DefaultableDictionary(IDictionary<TKey, TValue> dictionary, TValue defaultValue) {
            this.dictionary = dictionary;
            this.defaultValue = defaultValue;
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
            return dictionary.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public void Add(KeyValuePair<TKey, TValue> item) {
            dictionary.Add(item);
        }

        public void Clear() {
            dictionary.Clear();
        }

        public bool Contains(KeyValuePair<TKey, TValue> item) {
            return dictionary.Contains(item);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            dictionary.CopyTo(array, arrayIndex);
        }

        public bool Remove(KeyValuePair<TKey, TValue> item) {
            return dictionary.Remove(item);
        }

        public int Count {
            get { return dictionary.Count; }
        }

        public bool IsReadOnly {
            get { return dictionary.IsReadOnly; }
        }

        public bool ContainsKey(TKey key) {
            return dictionary.ContainsKey(key);
        }

        public void Add(TKey key, TValue value) {
            dictionary.Add(key, value);
        }

        public bool Remove(TKey key) {
            return dictionary.Remove(key);
        }

        public bool TryGetValue(TKey key, out TValue value) {
            if (!dictionary.TryGetValue(key, out value)) {
                value = defaultValue;
            }

            return true;
        }

        public TValue this[TKey key] {
            get
            {
                try
                {
                    return dictionary[key];
                } catch (KeyNotFoundException) {
                    return defaultValue;
                }
            }

            set { dictionary[key] = value; }
        }

        public ICollection<TKey> Keys {
            get { return dictionary.Keys; }
        }

        public ICollection<TValue> Values {
            get
            {
                var values = new List<TValue>(dictionary.Values) {
                    defaultValue
                };
                return values;
            }
        }
    }

    public static class DefaultableDictionaryExtensions {
        public static IDictionary<TKey, TValue> WithDefaultValue<TValue, TKey>(this IDictionary<TKey, TValue> dictionary, TValue defaultValue ) {
            return new DefaultableDictionary<TKey, TValue>(dictionary, defaultValue);
        }
    }
}

This project is a simple decorator for an IDictionary object and an extension method to make it easy to use.

The DefaultableDictionary will allow for creating a wrapper around a dictionary that provides a default value when trying to access a key that does not exist or enumerating through all the values in an IDictionary.

Example: var dictionary = new Dictionary<string, int>().WithDefaultValue(5);

Blog post on the usage as well.

sehe
  • 374,641
  • 47
  • 450
  • 633
John Sonmez
  • 7,128
  • 5
  • 37
  • 56
  • Please provide the context supporting the github link directly in your answer, or it will likely be removed. – Tim Post Aug 15 '11 at 06:28
  • 4
    Would be helpful if you didn't delete the links that provided clarity as well. – John Sonmez Aug 15 '11 at 13:45
  • 8
    I'd write the index getter as: `public TValue this[TKey key] { get { TValue value; TryGetValue(key, out value); return value; }` To avoid the exception handling. – James Curran Jun 28 '12 at 13:37
  • 2
    I like that you used an extension method to create the default dictionary instead of requiring your constructor be used (as I did). – Rob Sutherland Sep 19 '13 at 20:23
  • 8
    Exception-based flow control... yikers! That's a serious anti-pattern. Please change your indexer implementation to use `TryGetValue` instead of `try ... catch`. – ErikE Aug 28 '15 at 18:10
  • It is also important to convert `TValue value` into `Func value`. And indexer should add a value to dictionary if it was not found. Otherwise you will have problems if you would want to populate dictionary with reference types. This: `var dict = new DefaultDictionary>(emptyDict, new List()); dict["hello"].Add(3);` won't work – Archeg Sep 02 '16 at 08:05
  • 1
    This seems so much overkill just to get a default value for non-existing keys. – Ian Kirkpatrick Jun 07 '18 at 18:09
  • 1
    If you want to implement it by yourself you can create a simple Extension Method instead of building your own class – nrofis Jun 18 '20 at 15:40
  • This looks like the kind of a thing that gets discovered while trying to figure out a performance issue. – julx Feb 22 '22 at 23:14
6

No, nothing like that exists. The extension method is the way to go, and your name for it (GetValueOrDefault) is a pretty good choice.

Patrick Karcher
  • 22,995
  • 5
  • 52
  • 66