2

I'm trying to get some values from a list. But I want to assure that if key doesn't exist, it will return a default value 0 instead of throwing exception.

var businessDays = days.Where(x => x.Year == year).ToDictionary(x => x.Month, x => x.Qty);

var vmBusinessDays = new BusinessDay()
{
    Jan = businessDays[1],
    Feb = businessDays[2],
    Mar = businessDays[3],
    Apr = businessDays[4],
    May = businessDays[5]
    [..]
};

How is possible to use something like Nullable<T>.GetValueOrDefault or null-coalescing without polluting too much the code?

var vmBusinessDays = new BusinessDay()
{
    Jan = businessDays[1].GetValueOrDefault(),
    Feb = businessDays[1]?? 0
}

I'm aware of Dictionary<TKey, TValue>.TryGetValue, but it will duplicate code lines for setting output value to each property.

Andre Figueiredo
  • 12,930
  • 8
  • 48
  • 74
  • 1
    Well, the null coalescing operator version simply doesn't work because a `KeyNotFound` exception will be caught. Your other version is flawed for the same reason, as well as the obvious issue of calling `GetValueOrDefault` on the *value* type of the dictionary, not the dictionary itself. This is why `TryGetValue` exists... – Ed S. Feb 25 '14 at 19:40
  • As I stated in question... I hoped that was a simple workaround that I wasn't realizing, what I didn't realize it to wrapping it into an extension. – Andre Figueiredo Feb 25 '14 at 19:59

2 Answers2

3

You can define your own extension method:

public static class DictionaryExtensions
{
    public static TValue GetValueOrDefault<TKey, TValue>(
        this IDictionary<TKey, TValue> dict, TKey key)
    {
        TValue val;
        if (dict.TryGetValue(key, out val))
            return val;
        return default(TValue);
    }
}

You could then use it like so:

var vmBusinessDays = new BusinessDay()
{
    Jan = businessDays.GetValueOrDefault(1),
    Feb = businessDays.GetValueOrDefault(2)
}
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • 6
    `dict.TryGetValue(key, out val); return val;` should be enough. `TryGetValue` is specified as setting `val` to `default(TValue)` when it returns false. –  Feb 25 '14 at 19:20
  • Thanks! exactly what I needed, elegant code that keeps code simple. – Andre Figueiredo Feb 25 '14 at 20:00
  • @hvd: For this specific case, you're right. However, there's an implicit pattern behind `bool Try…(x, out y)` methods wherein the `out` value should be discarded if the method returns `false`, since it might be undefined (as in the case of [`Char.TryParse`](http://msdn.microsoft.com/en-us/library/system.char.tryparse(v=vs.110).aspx)). In my opinion, the current version of the code is clearer and safer. – Douglas Feb 25 '14 at 20:53
  • @Douglas That's a fair point, but in my experience, `char.TryParse` is an exception, the norm is actually that the output value is always defined, even if the method returns `false`. [`int.TryParse`](http://msdn.microsoft.com/en-us/library/f02979c7%28v=vs.110%29.aspx), [`double.TryParse`](http://msdn.microsoft.com/en-us/library/994c0zb1%28v=vs.110%29.aspx), [`decimal.TryParse`](http://msdn.microsoft.com/en-us/library/9zbda557%28v=vs.110%29.aspx), [`DateTime.TryParse`](http://msdn.microsoft.com/en-us/library/ch92fbc1%28v=vs.110%29.aspx), ... –  Feb 25 '14 at 21:39
  • @Douglas ... [`bool.TryParse`](http://msdn.microsoft.com/en-us/library/system.boolean.tryparse%28v=vs.110%29.aspx) are all examples where the output value is defined even on failure. At any rate, `IDictionary<,>.TryGetValue` and `Dictionary<,>.TryGetValue` to guarantee a specific result, so your version of the code isn't *safer*, but I can accept that you do consider it clearer, and that you won't be alone in that. –  Feb 25 '14 at 21:39
  • 1
    @hvd: Yes. I'd been under the impression that undefined out values were more common, but I couldn't find any other examples. By "safer", I meant it's less likely to break if someone implements `IDictionary` such that it (incorrectly) returns a non-default value for absent keys. – Douglas Feb 26 '14 at 09:52
2

In C# 7.0 or above you can use the following one liner:

public Item GetOrDefault(Key key) => 
    TryGetValue(key, out var item) ? item : default(Item);

If you want to create the key value pair in the dictionary, you can do the following:

public Item GetOrCreate(Key key) => 
        TryGetValue(key, out var item) ? item : dictionary[key] = default(Item);

or

public Item GetOrCreate(Key key) => 
            TryGetValue(key, out var item) ? item : dictionary[key] = new Item();
Greg
  • 1,549
  • 1
  • 20
  • 34