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?