4

I want a dictionary that would return a specified value for any key not in the dictionary, something like:

var dict = new DictWithDefValues("not specified");
dict.Add("bob78", "Smart");
dict.Add("jane17", "Doe");
Assert.AreEqual(dict["xxx"], "not specified");

Extending System.Collections.Generics.Dictionary and overriding TryGetValue doesn't work because TryGetValue isn't virtual.

Reimplementing Dictionary from scratch (from IDictionary<,>) is too much efforts.

Extension method won't let me "initialize" the dictionary with a default value. I want the consumer of dictionary to think that key is there, not just do dict.GetOrDefault(key, "not specified");

THX-1138
  • 21,316
  • 26
  • 96
  • 160
  • I am in the same situation. I have a huge collection of various business object types. I want to make extreme use of the strategy pattern by having dictionaries of Funcs that should either return standard Funcs or a special case Func in case the business object type warrants it. Open Closed Principle. What did you end up using here? – Tormod Mar 20 '11 at 17:43
  • Since my dictionaries were not big, I just fill in missing entries. – THX-1138 Mar 21 '11 at 15:40

6 Answers6

8

Reimplementing Dictionary from scratch (from IDictionary<,>) is too much efforts

This is really the best option. Just encapsulate a Dictionary<,> as a member of your class, and pass all members to the Dictionary's code. You only need to handle the properties and methods you wish to be different in that case.

I agree that it's a bit of tedious work - but it's probably less than 5 minutes worth of typing, since every method can be passed to the internal dictionary's implementation.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
6

I think Reed is correct that Reimplementing Dictionary is pretty simple and if that's what you need that's fine.

but it still seems like overkill to create a new class to replace

        dict.TryGetValue("xxx", out value);
        value = value ?? "not specified";

with

      value  = dict.GetOrDefault(key, "not specified")
Conrad Frix
  • 51,984
  • 12
  • 96
  • 155
  • 5
    well, the problem is that I have few consumers who use a dictionary passed to them. Those consumers do not know (and must not know, otherwise you get an SRP violation)what is the default value for missing entry. So no, its not just replacing that, its replacing: `someOtherServiceResponsibleForHandlingMissingKeys.GetValueFor(key)` – THX-1138 Nov 02 '10 at 16:49
  • Then you definitely need to go with Reed's approach – Conrad Frix Nov 02 '10 at 17:25
2

This extension method gets me through;

    public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> source, TKey key, TValue defaultValue = default(TValue))
    {
        TValue found;
        if (source.TryGetValue(key, out found))
        {
            return found;
        }
        else
        {
            return defaultValue;
        }
    }

It's called like so;

dict.GetValueOrDefault("foo", 0) // returns the key under "foo", or 0 if missing.
Steve Cooper
  • 20,542
  • 15
  • 71
  • 88
0

Try this

public class DefaultDictionary<Tkey,Tvalue>
    where Tkey: IEquatable<Tkey>
{
    private readonly Func<Tvalue> _getDefault;
    private Dictionary<Tkey, Tvalue> _dict;

    public DefaultDictionary(Func<Tvalue> getDefault = null)
    {
        if (getDefault == null)
        {
            _getDefault = () => default(Tvalue);
        }
        else
        {
            _getDefault = getDefault;
        }
        _dict = new Dictionary<Tkey, Tvalue>();
    }

    public Tvalue this[Tkey key]
    {
        get
        {
            if (!_dict.ContainsKey(key))
            {
                _dict[key] = _getDefault();
            }
            return _dict[key];
        }

        set { _dict[key] = value; }
    }

    public bool ContainsKey(Tkey key)
    {
        return _dict.ContainsKey(key);
    }

    public override string ToString()
    {
        var sb = new StringBuilder();
        foreach (var kvp in _dict)
        {
            sb.AppendFormat("[{0}] : {1}", kvp.Key.ToString(), kvp.Value.ToString()).AppendLine();
        }

        return sb.ToString();
    }
}

You can easily add wrappers to the other methods and properties as needed.

Simon
  • 2,840
  • 2
  • 18
  • 26
  • I think OP wanted something that implemented IDictionary. Also, I think `if (!_dict.ContainsKey(key)) { ... }` can be slightly more efficiently implemented with `if (!dict.TryGetValue(key, out v)) { ... }`. I'm not sure if it makes sense to automatically add an entry to the underlying map when the lookup fails. I checked Scala's mutable map, and it doesn't exhibit that behavior. But I could see that going either way. Maybe it should be a constructor setting. – Daniel Yankowsky Jul 03 '14 at 15:30
0

I notice your example is a dictionary of Strings. It is also strings in my own scenario where I want similar behavior.

Perhaps the good old StringDictionary class will do what you want. Unlike Dictionary<string,string> its get_Item method returns null for keys which do not exist in the dictionary.

Tim Lovell-Smith
  • 15,310
  • 14
  • 76
  • 93
0

I created a DefaultableDictionary in .NET that does exactly what you are asking for!

http://github.com/jsonmez/Defaultable-Dictionary

Blog post here.

John
  • 15,418
  • 12
  • 44
  • 65
John Sonmez
  • 7,128
  • 5
  • 37
  • 56