0

I want to mimic the Python collections.defaultdict in C#. The following works fine as long as the value type has a parameterless constructor:

public class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TValue : new()
{
    public new TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val)) {
                val = new TValue();
                Add(key, val);
            }
            return val;
        }
        set { base[key] = value; }
    }
}

But what if I want to use a constructor that takes the key as argument? Or generally, a factory function that, given the key, returns an instance of the value type?

public class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TValue : new()
{
    private readonly Func<TKey, TValue> factory;

    public DefaultDictionary() : this(key => new TValue())
    {}

    public DefaultDictionary(Func<TKey, TValue> factory)
    {
        this.factory = factory;
    }

    public new TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val)) {
                val = factory(key);
                Add(key, val);
            }
            return val;
        }
        set { base[key] = value; }
    }
}

Now the problem is that TValue is still required to have a parameterless constructor, even when a factory function is used. The following won't compile if Thingy does not have a parameterless constructor:

new DefaultDictionary<int, Thingy>(key => new Thingy(key, otherStuff, moreStuff));

However, removing the constraint will cause error CS0304 in the new TValue() statement.

Intuitively I would want to place the type constraint on the DefaultDictionary() constructor, but I doubt this is possible. Is there a proper way to solve this problem in C#?

9
  • You could go with "key = default(TValue)" or "val = default(TValue)". Commented Mar 23, 2021 at 13:30
  • This would simply assign null to val. Commented Mar 23, 2021 at 13:31
  • Or default value for struct. I don't see other way to do it, but maybe it's just my imagination not being creative enough :-) One idea would be to remove a default constructor from DefaultDictionary and thus removing usage of default constructor of TValue Commented Mar 23, 2021 at 13:35
  • Yes, but having to explicitly write the initializer lambda in every creation of DefaultDictionary would be quite impractical... Commented Mar 23, 2021 at 13:36
  • 3
    you may create another class and static method for creation like this (also using new to hide method has obvious downsides) Commented Mar 23, 2021 at 13:46

1 Answer 1

2

One way is to keep only the constructor taking the value factory in DefaultDictionary, and create a subclass that does have the : new() constraint, and move the parameterless constructor there:

public class DefaultDictionaryParameterless<TKey, TValue> :
    DefaultDictionary<TKey, TValue> where TValue : new()
{
    public DefaultDictionaryParameterless()
        : base(x => new TValue())
    {
        
    }
}

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

    public DefaultDictionary(Func<TKey, TValue> factory)
    {
        this.factory = factory;
    }
    ...
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.