Passing generic parameter results in wrong overloa

2019-03-03 07:38发布

I'm trying to update Medusa to allow decorated POCOs to be used anywhere it currently uses List<DbParameter>. The problem I'm running into is that the wrong overload is being called. Here is a simple example of what I'm seeing:

void Run()
{
    CallDoSomething<Program>("Hello World", new object());
    CallDoSomething<Program>("Hello World2", new List<int>());
}

// `DoSomething<T>` represents the functions that do the heavy lifting
public T DoSomething<T>(string someString, List<int> ints) where T : class
{
    Console.WriteLine("List<int>: {0}", someString);
    return default(T);
}
public T DoSomething<T>(string someString, object ints) where T : class
{
    Console.WriteLine("object: {0}", someString);
    // In my real implementation, this turns the object to a typed List<T>
    // and passes it to the previous overload.
    return default(T);
}

// We're trying to refactor the code in this method to reduce code duplication in
// the `CallDoSomething<T>` methods that will actually be called by the end user
internal T CallDoSomething<T, U>(string someString, U ints) where T : class
{
    // Do a bunch of stuff here that would otherwise be duplicated by the `CallDoSomething<T>` methods
    return DoSomething<T>(someString, ints);
}

public T CallDoSomething<T>(string someString, List<int> ints) where T : class
{
    return CallDoSomething<T, List<int>>(someString, ints);
}
public T CallDoSomething<T>(string someString, object ints) where T : class
{
    return CallDoSomething<T, object>(someString, ints);
}

In this case, the resulting output is:

object: Hello World
object: Hello World2

While I was expecting it to be:

object: Hello World
List<int>: HelloWorld2

It kind of makes sense that both cases were directed to the overload taking an object parameter since both of them are objects. I suspect this is happening because (from what I know) Generics and overload resolution are handled at compile time rather than runtime.

The first alternative that came to me was to use Reflection to invoke the call dynamically in CallDoSomething<T, U>, but that felt too dirty. Instead the solution I've come up with involves passing a delegate to CallDoSomething<T, U> which seems to work. Here's what it looks like:

void Run()
{
    CallDoSomething<Program>("Hello World", new object());
    CallDoSomething<Program>("Hello World2", new List<int>());
}

public T DoSomething<T>(string someString, List<int> ints) where T : class
{
    Console.WriteLine("List<int>: {0}", someString);
    return default(T);
}
public T DoSomething<T>(string someString, object ints) where T : class
{
    Console.WriteLine("object: {0}", someString);
    return default(T);
}

internal delegate T DoSomethingDelegate<T, U>(string someString, U ints) where T : class;
internal T CallDoSomething<T, U>(string someString, U ints, DoSomethingDelegate<T, U> doSomething) where T : class
{
    // Do a bunch of stuff here that would otherwise be duplicated by the `CallDoSomething<T>` methods
    return doSomething(someString, ints);
}

public T CallDoSomething<T>(string someString, List<int> ints) where T : class
{
    return CallDoSomething<T, List<int>>(someString, ints, DoSomething<T>);
}
public T CallDoSomething<T>(string someString, object ints) where T : class
{
    return CallDoSomething<T, object>(someString, ints, DoSomething<T>);
}

This seems to work and it removes a large amount of code duplication, but it makes the code fairly convoluted. Is there a better way to approach this problem?

标签: c# oop
1条回答
▲ chillily
2楼-- · 2019-03-03 08:04

Yes, the overload is resolved at compile-time. You can force it to be evaluated at execution time if you're using C# 4, like this:

internal T CallDoSomething<T, U>(string someString, U ints) where T : class
{
    dynamic d = ints;
    return DoSomething<T>(someString, d);
}

However, personally I'd try to simplify your design if you possibly can. This sort of thing gets messy very quickly.

查看更多
登录 后发表回答