Generic method takes KeyValuePair. How do I forward the call to the correct overload taking the Key type?

Viewed 118
 class Program
    {
        static void Main(string[] args)
        {

        }

        void DoSomething(int a)
        {
            Console.WriteLine("int");
        }
        void DoSomething(char a)
        {
            Console.WriteLine("char");
        }
        void DoSomething<T, U>(KeyValuePair<T, U> a)
        {
            DoSomething(a.Key);
        }
    }

I have a third-party assembly that has a huge struct with lots of members of different types in it. I need to take some of the values from that struct and process them. So I write overloads for different types. I know what I need to do with ints, with chars, etc. And if some value is a key value pair, I know I need to process only the key the same way I would process a primitive value. The code above is my failed attempt to do so. The problem is that the C# compiler complains that it cannot determine which overload to call for a.Key because, unlike C++, where it would instantiate the template and then automagically know exactly which overload to call or fail with a compiler error, C# doesn't do this.

Is there something basic I'm missing, and if not, what is the usual C# idiom to solve the design problem I'm facing?

4 Answers

If you are 100% confident that T will be int or char - then dynamic is one approach to consider:

static void DoSomething<T, U>(KeyValuePair<T, U> a)
{
    dynamic bob = a.Key;
    DoSomething(bob);
}

One hacky way is to cast a.Key to dynamic:

DoSomething((dynamic)a.Key);

The correct overload will be determined at runtime. If there are no appropriate overloads then you will get an exception.

The problem with e.g. the dynamic approach is that it gives you a runtime error. Having generic code that compiles but produces a runtime error due to type mismatches always feels like a "lie" to me - if you cannot express it as a type parameter constraint and have the compiler fail the compilation, you shouldn't write that generic method.

I would instead supply two methods:

    void DoSomething<U>(KeyValuePair<int, U> a)
    {
        DoSomething(a.Key);
    }
    void DoSomething<U>(KeyValuePair<char, U> a)
    {
        DoSomething(a.Key);
    }

Now you can only supply KeyValuePairs that we know how to correctly deal with, and anything else goes back to generating compile-time errors. I prefer the slight redundancy1 to get the error at compile time.


Of course, in the above, the utility of these methods is somewhat questionable but in a larger code base you may still be able to abstract some functionality into a private generic method that both of these DoSomething methods defer to.


1And of course there's only "apparent" redundancy in the source code. In the compiled IL for these methods, the references to the other DoSomething methods are different, since overload resolution will produce different results.

As of C# 7.1, you can use the is operator with an output variable to accomplish this. This still has the drawback of dynamic in that the error thrown will be a runtime error vs a compile time error but it eliminates the CLR overhead of using dynamic.

private void DoSomething<T, U>(KeyValuePair<T, U> a)
{
    if (a.Key is int keyInt)
    {
        DoSomething(keyInt);
    }
    else if (a.Key is char keyChar)
    {
        DoSomething(keyChar);
    }
    else
    {
        throw new Exception("Unable to DoSomething to a.Key");
    }
}

And the same thing, but prettier because switch statement.

private void DoSomething<T, U>(KeyValuePair<T, U> a)
{
    switch (a.Key)
    {
        case int keyInt:
            DoSomething(keyInt);
            break;
        case char keyChar:
            DoSomething(keyChar);
            break;
        default:
            throw new Exception("Unable to DoSomething to a.Key");
    }
}
Related