0
\$\begingroup\$

I have an API that returns a dictionary of dictionaries.

I currently have this code to access the value of the dictionary, but it looks like a code smell. Is there a better way to structure this?

// Dictionary<string, Dictionary<DateTime, string>> CountryCode();

var response = api.CountryCode();

string GB2001;
if (response.ContainsKey("GB") && response["GB"].ContainsKey(new DateTime(2001, 04, 01)))
{
    GB2001 = response["GB"][new DateTime(2001, 04, 01)];

    // Use the result of GB2001 here
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ This simply is an example of access the GB2001 variable. \$\endgroup\$ Jun 25, 2019 at 14:53
  • \$\begingroup\$ Thanks @dfhwze I have fixed the declaration \$\endgroup\$ Jun 25, 2019 at 14:54
  • \$\begingroup\$ This is an example of accessing the library I am using. This result returns a dictionary of dictionaries. Sure, I will add the response type explicitly. \$\endgroup\$ Jun 25, 2019 at 14:55
  • \$\begingroup\$ If you can change CountryCode(), please do so. Give it a proper name -- check out docs.microsoft.com/en-us/dotnet/standard/design-guidelines/… -- and reconsider returning a nested dictionary. Why not return a custom class with meaningful property names? \$\endgroup\$
    – BCdotWEB
    Jun 26, 2019 at 9:57

2 Answers 2

4
\$\begingroup\$

@dfhwze's answer is great (upvoted it),

there are also other options, which refactors a bit more. These could be a option if you need the more than once.

Inheritance

Create a separate class and add the method there.

This is a bit more "information hiding" (OO concept)

e.g.

public class CountryCodes : Dictionary<string, Dictionary<DateTime, string>>
{
    public string FindGB2001(string countryCode, DateTime date)
    {
        if (TryGetValue(countryCode, out var country)
            && country != null &&
            country.TryGetValue(date, out var GB2001))
        {
            return GB2001;
        }

        return null;
    }
}

usage:

CountryCodes countryCodes = ...;
countryCodes.FindGB2001("GB", new DateTime(2001, 04, 01));

Generic extension

Another option is to create a helper extension for nested dictionaries. This is nice if you need it for more situations. You could also call if just TryFind.

public static class DictionaryExtensions
{
    public static bool TryFindInNested<TKey, TKey2, TItem>(this Dictionary<TKey, IDictionary<TKey2, TItem>> dict, TKey key1, TKey2 key2, out TItem item)
    {
        if (dict.TryGetValue(key1, out var item1) && item1 != null && item1.TryGetValue(key2, out item))
        {
            return true;
        }

        item = default(TItem); //or just "default" for C# 7.1
        return false;
    }
}

Usage:

var countyCodes2 = new Dictionary<string, IDictionary<DateTime, string>>(); // note used IDictionary for nested.
countyCodes2.TryFindInNested("GB", new DateTime(2001, 04, 01), out string item);
\$\endgroup\$
3
\$\begingroup\$

The 'problem' with this approach is that you perform a lookup twice for each key.

  1. ContainsKey
  2. indexer lookup [..]

code

if (response.ContainsKey("GB") && response["GB"].ContainsKey(new DateTime(2001, 04, 01)))
{
    GB2001 = response["GB"][new DateTime(2001, 04, 01)];
}

Use TryGetValue to combine lookup and indexer.

string GB2001;
if (response.TryGetValue("GB", out var country))
{
    country?.TryGetValue(new DateTime(2001, 04, 01), out GB2001);
}
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.