Nested switch to return configuration for combination of 3 database types and 4 domains I am currently doing a code review for a coworker.

I have encountered something like this:

switch (type)
{
    case DatabaseType.Type1:
        switch (domain)
        {
            case DOMAIN_1:
                return DatabaseConfigType.Type1Domain1;
            case DOMAIN_2:
                return DatabaseConfigType.Type1Domain2;
            case DOMAIN_3:
                return DatabaseConfigType.Type1Domain3;
            case DOMAIN_4:
                return DatabaseConfigType.Type1Domain4;
            default:
                Trace.Error("Undefined Type1 domain :" + domain);
                throw new ArgumentOutOfRangeException("domain", domain, null);
        }
    case DatabaseType.Type2:
        switch (domain)
        {
            case DOMAIN_1:
                return DatabaseConfigType.Type2Domain1;
            case DOMAIN_2:
                return DatabaseConfigType.Type2Domain2;
            case DOMAIN_3:
                return DatabaseConfigType.Type2Domain3;
            case DOMAIN_4:
                return DatabaseConfigType.Type2Domain4;
            default:
                Trace.Error("Undefined Type2 domain :" + domain);
                throw new ArgumentOutOfRangeException("domain", domain, null);
        }
    case DatabaseType.Type3:
        switch (domain)
        {
            case DOMAIN_1:
                return DatabaseConfigType.Type3Domain1;
            case DOMAIN_2:
                return DatabaseConfigType.Type3Domain2;
            case DOMAIN_3:
                return DatabaseConfigType.Type3Domain3;
            case DOMAIN_4:
                return DatabaseConfigType.Type3Domain4;
            default:
                Trace.Error("Undefined Type3 domain :" + domain);
                throw new ArgumentOutOfRangeException("domain", domain, null);
        }
    default:
        Trace.Error("Undefined type :" + type);
        throw new ArgumentOutOfRangeException("type", type, null);
}


Obviously, Types and Domains have actual wordy names, I just changed them for the question because they are irrelevant and probably sensitive information. On the other hand, the actual names for the DatabaseConfigType Enum are literally of the format TypeDomain, with the actual names of the type and domain instead of my numbered version.

I see this code, and it just feels wrong. There is some obvious redundancy by having three times (possibly more in the future) the exact same switch conditions inside each "outer" switch case, but since they return an Enum with different values, I just can't figure out a way to actually make this better.

The enum is in no particular order, and can't be expected to stay the same, so I can't do some math-ish unreadable but fast solution, not that I think it would be better anyway.

The code is probably as fast as it can be while still being readable, I'm mostly looking for a way to reduce the copy-paste aspect, therefore the lines of code, because I can't shake the feeling that there has to be a better way. If there isn't, feel free to tell me that.
Just declare a (static readonly) dictionary:

var values = new Dictionary&lt;(DatabaseType, DomainType), DatabaseConfigType&gt;()
{
    { (DatabaseType.Type1, DomainType.Domain1), DatabaseConfigType.Type1Domain1 }
};


Filled with all possible combinations. Then your function will be reduced to:

DatabaseConfigType GetDatabaseConfiguration(DatabaseType database, DomainType domain)
    =&gt; values[(type, domain)];


You may want to use TryGetValue() to customize error message or to throw a different, more informative, exception. I'm using C# 7 named tuples but you can do it without them, it's just little bit more verbose because you need to declare your own Tuple because we need it to be a struct unless you use a custom comparer.



Edit: what if you need custom error messages (saying which part is unknown)? You need to nest dictionaries:

var values = new Dictionary&lt;DatabaseType, Dictionary&lt;DomainType, DatabaseConfigType&gt;&gt;()
{
    {
        DatabaseType.Type1, Dictionary&lt;DomainType, DatabaseConfigType&gt;
        {
            { DomainType.Domain1, DatabaseConfigType.Type1Domain1 }
        }
    }
};


In this case to get a value:

DatabaseConfigType GetDatabaseConfiguration(DatabaseType database, DomainType domain)
{
    if (values.TryGetValue(database, out var configTypes))
    {
        if (configTypes.TryGetValue(domain, out var configType))
            return configType;

        throw new ArgumentException($"Unknown domain {domain} for {database}");
    }

    throw new ArgumentException($"Unknown database {database}");
}




Which exception should be thrown? Assuming you can change this part of the code contract I feel ArgumentOutOfRangeException is not the appropriate one in this case (POLA?). Some combinations aren't allowed, an enum may have an invalid value (GetDatabaseConfiguration((DatabaseType)333, (DomainType)444)) or you may simply forgot to update this function after an update to the enum. 
In my opinion a generic ArgumentException may be enough (unless you want to differentiate the invalid combination case). A specific exception exists (InvalidEnumArgumentException) but I seldom saw it in real-world code (probably because it doesn't add much value over ArgumentException).



Assuming you changed names but not style then DOMAIN_1 isn't a good name because it doesn't follow common C# naming conventions: it's all uppercase and it uses underscore as separator, it should be Domain1.