4

It really annoys me that I must use .ContainsKey instead of just doing a value==null in a dictionary. Real life example:

var dictionary = new Dictionary<string, FooBar>();
var key = "doesnt exist";
var tmp = dictionary[key] ?? new FooBar(x, y);

Instead, I have these choices:

var key = "doesnt exist";
FooBar tmp = null;
if (dictionary.ContainsKey(key))
{
    tmp = dictionary[key];
} else {
    tmp = new FooBar(x, y);
}

Or this:

FooBar tmp = null;
if (!Dictionary.TryGetValue(key, out tmp))
{
    tmp = new FooBar(x, y);
}

To me, this code is extremely verbose. Is there a built-in generic class which implements IDictionary (or otherwise allows key-value type access) but doesn't throw an exception when I try to lookup a key, and instead return null?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Earlz
  • 62,085
  • 98
  • 303
  • 499
  • 3
    Why not create your own extension method? – Dave Zych Oct 17 '12 at 21:25
  • 2
    The dictionary works like this, because there is difference between containing a null value and not containing a value at all. You would need something like two types of null. Otherwise you could not distinguish whether the value is null or the value does not exist – Ondra Oct 17 '12 at 21:26
  • You could perhaps create a single-parameter `TryGetValue` extension method which behaves as you describe. It's too bad `TryGetValue` uses an `out` parameter rather than `ref`, and can't simply leave the variable alone when the key doesn't exist, since then one could simply initialize the variable with the desired default. If your goal was to add an item to the dictionary if it didn't already exist, you might consider `ConcurrentDictionary`, which has some interesting `GetOrAdd` methods. – supercat Oct 17 '12 at 21:29

3 Answers3

8

The problem is that Dictionary<TKey, TValue> allows you to store null as a value, thus you wouldn't know if the key didn't exist, or just had a value of null. The other problem is that null is only valid for TValue types that are reference types (or Nullable<T>).

Now, that said, you could write an extension method yourself to do so:

public static class DictionaryUtilities
{
    public static TValue SafeGet<TKey, TValue>(this Dictionary<TKey, TValue> dict, TKey key, TValue defaultIfNotFound = default(TValue))
    {
        TValue value;

        return dict.TryGetValue(key, out value) ? value : defaultIfNotFound;
    }
}

In this way, you could query your dictionary like this:

var sample = new Dictionary<int, string> { {1, "One"}, {2, "Two}, {3, "Three"} };

var willBeNull = sample.SafeGet(4);

var willBeHi = sample.SafeGet(4, "HI!");

Though, of course, the above code would return the default of value types if TValue was a value type (that is, if the value type was int, it would return 0 on not found, by default.)

Again, though, you could create two forms, one where TValue is constrained to class and one to struct that would allow you to return a Nullable<T> for value types...

James Michael Hare
  • 37,767
  • 9
  • 73
  • 83
  • `out` keyword guarantees a variable to be initialized. So the `?` operator is not required here. Just three lines: `declare x; TryGet(out x); return x;` – abatishchev Oct 17 '12 at 21:33
  • @abatishchev: depends, if we want a user specified default, we'd need to set it after the call, since the `out` would stomp it. – James Michael Hare Oct 17 '12 at 21:36
  • I think the better solution would be to restrict it somehow so that you could only use that extension method with nullable types – Earlz Oct 18 '12 at 00:59
  • If you want to provide a default value - yes. Otherwise it will have just `default(T)`, as you mentioned in the optional parameter. – abatishchev Oct 18 '12 at 02:08
  • @Earlz: True, regardless the point is the OP is free to create the method in whatever flavor they like. – James Michael Hare Oct 18 '12 at 13:37
2

For a generic collection null is not always a valid value and you can't write a generic method that returns null. it must be default(T).

You can easily create an utility method that returns default(T) if you are sure you will not use it as value

L.B
  • 114,136
  • 19
  • 178
  • 224
0

If you extend then you have to pass a default every time.
That may be what you want.
Could implement Dictionary and pass a default value in the ctor.

public class DictionaryWithDefault<T1,T2> : Dictionary<T1,T2>
{   
    private T2 t2Default;
    public new T2 this[T1 t1]
    {   // indexer - if the key is not present return the default
        get
        {
            T2 t2t;
            return TryGetValue(t1, out t2t) ? t2t : t2Default;
        }
        set {
            base[t1] = value;
        }
    }
    public DictionaryWithDefault(T2 _t2) { this.t2Default = _t2; }
}

If you want the Dictionary to not allow a value of null then you override Add.

Borrowed some syntax from JamesMH +1

Where I use something like this is a Dictionary with a composite key.

Where this suffers is if you change the default value then it changes the t2Default value.
I am working on that.
If T2 is a value type then this works.
If T2 is a reference type then would need to make a deep copy and it is not worth the hassle.

Community
  • 1
  • 1
paparazzo
  • 44,497
  • 23
  • 105
  • 176