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

C#.NetHashDictionary

C# Problem Overview


The indexer into Dictionary throws an exception if the key is missing. Is there an implementation of IDictionary that instead will return default(T)?

I know about the TryGetValue() method, but that's impossible to use with LINQ.

Would this efficiently do what I need?:

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

I don't think it will as I think it will iterate the keys instead of using a Hash lookup.

C# Solutions


Solution 1 - C#

Indeed, that won't be efficient at all.

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

For earlier versions you can write the extension method yourself:

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

Or with C# 7.1:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key) =>
    dictionary.TryGetValue(key, out var ret) ? ret : default;

That uses:

  • An expression-bodied method (C# 6)

  • An out variable (C# 7.0)

  • A default literal (C# 7.1)

Solution 2 - C#

If you're using .NET Core 2 or above (C# 7.x), the CollectionExtensions class is introduced and you can use the 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);

Solution 3 - C#

Carrying these extension methods can help..

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

Solution 4 - C#

Collections.Specialized.StringDictionary provides a non-exception result when looking up a missing key's value. It is also case-insensitive by default.

Caveats

It is only valid for its specialized uses, and — being designed before generics — it doesn't have a very good enumerator if you need to review the whole collection.

Solution 5 - C#

If you're using .Net Core, you can use the CollectionExtensions.GetValueOrDefault method. This is the same as the implementation provided in the accepted answer.

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

Solution 6 - C#

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

Solution 7 - C#

One could define an interface for the key-lookup function of a dictionary. I'd probably define it as something like:

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

The version with non-generic keys would allow code that was using code using non-structure key types to allow for arbitrary key variance, which would not be possible with a generic type parameter. One should not be allowed to use a mutable Dictionary(Of Cat, String) as a mutable Dictionary(Of Animal, String) since the latter would allow SomeDictionaryOfCat.Add(FionaTheFish, "Fiona"). But there's nothing wrong with using a mutable Dictionary(Of Cat, String) as an immutable Dictionary(Of Animal, String), since SomeDictionaryOfCat.Contains(FionaTheFish) should be considered a perfectly well-formed expression (it should return false, without having to search the dictionary, for anything that isn't of type Cat).

Unfortunately, the only way one will be able to actually use such an interface is if one wraps a Dictionary object in a class which implements the interface. Depending upon what you're doing, however, such an interface and the variance it allows might make it worth the effort.

Solution 8 - C#

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

Solution 9 - C#

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

Solution 10 - C#

Since .NET core 2.0 you can use:

myDict.GetValueOrDefault(someKeyKalue)

Solution 11 - C#

What about this one-liner that checks whether a key is present using ContainsKey and then returns either the normally retreived value or the default value using the conditional operator?

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

No need to implement a new Dictionary class that supports default values, simply replace your lookup statements with the short line above.

Solution 12 - C#

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.

Solution 13 - C#

This question helped to confirm that the TryGetValue plays the FirstOrDefault role here.

One interesting C# 7 feature I would like to mention is the out variables feature, and if you add the null-conditional operator from C# 6 to the equation your code could be much more simple with no need of extra extension methods.

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

The downside of this is that you can't do everything inline like this;

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

If we'd need/want to do this we should code one extension method like Jon's.

Solution 14 - C#

It could be a one-liner to check TryGetValue and return default value if it is 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

Solution 15 - C#

Modern Answer

As of .NET Core 2.0, there is a built-in extension method with 2 overloads:

TValue GetValueOrDefault<TKey,TValue>(TKey)
TValue GetValueOrDefault<TKey,TValue>(TKey, TValue)

Usage:

var dict = new Dictionary<string, int>();

dict.GetValueOrDefault("foo");     // 0: the datatype's default
dict.GetValueOrDefault("foo", 2);  // 2: the specified default

The first version returns null for nullable types, of course.

See the documentation for more detail.

Solution 16 - C#

In general I would support the answer from Jon Skeet, however I prefer an implementation where I can give the default value as parameter:

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

Solution 17 - C#

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

Solution 18 - C#

In .NET 5 core, GetValueOrDefault extension is available out of the box.

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

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
QuestionTheSoftwareJediView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#cdevView Answer on Stackoverflow
Solution 3 - C#nawfalView Answer on Stackoverflow
Solution 4 - C#Mark HurdView Answer on Stackoverflow
Solution 5 - C#theMayerView Answer on Stackoverflow
Solution 6 - C#BrianView Answer on Stackoverflow
Solution 7 - C#supercatView Answer on Stackoverflow
Solution 8 - C#Jone PolvoraView Answer on Stackoverflow
Solution 9 - C#neuralView Answer on Stackoverflow
Solution 10 - C#Antoine LView Answer on Stackoverflow
Solution 11 - C#Byte CommanderView Answer on Stackoverflow
Solution 12 - C#NetMageView Answer on Stackoverflow
Solution 13 - C#hardkodedView Answer on Stackoverflow
Solution 14 - C#Aryan FirouzianView Answer on Stackoverflow
Solution 15 - C#MarredCheeseView Answer on Stackoverflow
Solution 16 - C#Damian VogelView Answer on Stackoverflow
Solution 17 - C#Joel CoehoornView Answer on Stackoverflow
Solution 18 - C#Xavier JohnView Answer on Stackoverflow