Dictionary returning a default value if the key does not exist

C#CollectionsDictionary

C# Problem Overview


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?

C# Solutions


Solution 1 - C#

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 :)

Solution 2 - C#

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.

Solution 3 - C#

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.

Solution 4 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwasatzView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Rob SutherlandView Answer on Stackoverflow
Solution 3 - C#John SonmezView Answer on Stackoverflow
Solution 4 - C#Patrick KarcherView Answer on Stackoverflow