Remot H
Remot H

Reputation: 129

Initializing a dictionary to custom default values

I am trying to create a for loop that invokes a function of several instance of class A in a dictionary, and if there is no value for a key, it creates it and then invokes it. It seems to me as if there must be a way to create a value upon first access to a key.

I am currently using this code though I think it is not the best possible practice:

(dictionary[i] = dictionary.ContainsKey(arr[i]) ? dictionary[i] : new A()).Push(10);

Is there a cleaner for such a problem in C#?

Upvotes: 0

Views: 4585

Answers (2)

Martin Liversage
Martin Liversage

Reputation: 106796

ConcurrentDictionary has a GetOrAdd method (and other useful methods like AddOrUpdate, TryRemove etc.). If just a plain dictionary had GetOrAdd you could use that...

Luckily, you can create an extension method in a static class which you probably should name DictionaryExtensions:

public static TValue GetOrAdd<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    Func<TKey, TValue> valueFactory)
{
    if (dictionary == null)
        throw new ArgumentNullException(nameof(dictionary));
    if (key == null)
        throw new ArgumentNullException(nameof(key));
    if (valueFactory == null)
        throw new ArgumentNullException(nameof(valueFactory));

    if (dictionary.TryGetValue(key, out var existingValue))
        return existingValue;
    var value = valueFactory(key);
    dictionary.Add(key, value);
    return value;
}

How to use it:

dictionary.GetOrAdd(i, () => new A()).Push(10);

This version uses a value factory so that new A() is only executed in case it is required. Another ConcurrentDictionary.GetOrAdd() overload uses a value provided as parameter which you might consider as an alternative.

I find that creating extension methods like this that closely mirrors the methods on ConcurrentDictionary is very useful.

Upvotes: 2

welrocken
welrocken

Reputation: 256

I'd say a cleaner code would look something like this:

var key = arr[i];
var hasKey = dictionary.ContainsKey(key);

if (!hasKey)
    dictionary.Add(key, new A());

var itemToUse = dictionary[key];
itemToUse.Push(10);

Although it seems to me you are looking for something shorter. I guess what you are really asking is a short-hand method that does:

Returns the value for a given key if the key exists, else adds the key to the dictionary with some default value.

I think the above code tells a lot more about the intent, but in case you want something different, I can think of following two solutions.

The first one is an extension method for getting the item:

public static TValue Get<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
{
    var hasKey = dictionary.ContainsKey(key);

    if (!hasKey)
        dictionary.Add(key, defaultValue);

    return dictionary[key];
}

You would use it as:

dict.Get(arr[i], defaultValue: new A())
    .Push(10);

The second solution I can think of is a new derivative of Dictionary:

class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    private readonly Func<TKey, TValue> _defaultValueFactory;

    public DefaultDictionary(TValue defaultValue)
    {
        _defaultValueFactory = new Func<TKey, TValue>(x => defaultValue);
    }

    public DefaultDictionary(Func<TValue> defaultValueFactory)
    {
        _defaultValueFactory = new Func<TKey, TValue>(x => defaultValueFactory()) ?? throw new ArgumentNullException(nameof(defaultValueFactory));
    }

    public DefaultDictionary(Func<TKey, TValue> defaultValueFactory)
    {
        _defaultValueFactory = defaultValueFactory ?? throw new ArgumentNullException(nameof(defaultValueFactory));
    }

    public new TValue this[TKey key]
    {
        get
        {
            var hasKey = ContainsKey(key);

            if (!hasKey)
            {
                var defaultValue = _defaultValueFactory(key);
                Add(key, defaultValue);
            }

            return base[key];
        }
        set
        {
            base[key] = value;
        }
    }
}

The usage of this goes like:

var dictionary = new DefaultDictionary<string, A>(() => new A());
// ...
dictionary[arr[i]].Push(10);

I must warn you about something, this derivative of Dictionary hides the index operator. And since using IDictionary as types for members is a common practice (e.g. private IDictionary<string, A> dictionary as a member), you can't use the overloaded version without casting. So either cast your variable to DefaultDictionary every time you want to use the overloaded indexer, or have an interface for this new dictionary like:

interface IDefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    new TValue this[TKey key] { get; set; }
}

And have your members, variables use it as their defining type:

private IDefaultDictionary<string, A> dictionary;

But this also means as a concrete class you must now use DefaultDictionary, and that's the trade-off.

Upvotes: 1

Related Questions