There are several similar questions about generic methods with concrete overloads here on SO and most of them say essentially the same thing: Generic overload resolution is done at compile time so if you want to use a concrete overload, you may need to use dynamic in order to have the runtime decide which overload to use. OK, cool--I understand that. None of the questions I've found deal with an out parameter and I'm wondering if there's a better way to handle this than what I've done. Let's start with the most basic case (my code was all written and tested in LinqPad):
void Main()
{
string x = "";
var g = new GenericTest();
g.RunTest(x); //Ran from TryFoo<T>
g.RunTestWithDynamic(x);//Ran from TryFoo(string)
g.Foo(x); //Ran from TryFoo(string)
}
public class GenericTest
{
//public void RunTest(string withInput) => Foo(withInput); <-- This would fix it
public void RunTest<T>(T withInput) => Foo(withInput);
public void RunTestWithDynamic<T>(T withInput) => Foo((dynamic)withInput);
public void Foo<T>(T withInput) => Console.WriteLine("Ran from TryFoo<T>");
public void Foo(string withInput) => Console.WriteLine("Ran from TryFoo(string)");
}
Here are some things to note:
RunTest
is a generic method that calls another generic method,Foo
. When I callRunTest
, it appears the compiler doesn't follow all the way from the call site to see thatg.RunTeset
is passing inx
which is astring
and link it all up so that theFoo(string)
overload is called; instead, it just sees thatRunTest<T>
is callingFoo
. It doesn't make different "paths" based on different input values ofT
. OK, that's fair and understandable.- If I call
Foo
directly from myMain
method, the compiler is smart enough to see that we are callingFoo
with astring
directly and correctly selects the concrete overload. - As the linked SO posts describe, I can call
RunTestWithDynamic
which will change which overload is used at runtime based on value. It feels just a bit "hacky", but I'm good with this solution. - I've commented out a line: a concrete overload of
RunTest
. This would be essentially the same as callingFoo
directly. If that were un-commented, it would fix everything. Alas, that is not an option for me in my case.
Now, what if the T
is for an out
parameter? Consider the pattern used by, say, int.TryParse
where you return a bool
to indicate if it succeeded or not, but the value you actually want is an out
. Now you can't really do dynamic resolution because you can't cast an out parameter. I considered doing something where I make a default(T)
and then casting that to dynamic, but if that ever works well anywhere else, there is the problem of reference types that default to null to deal with. Nope, that doesn't work, either.
In the end, the best I could come up with was this:
void Main()
{
string x;
var g = new GenericTest();
g.TryRunTest(out x); //Ran from TryFoo<T>
g.TryRunTestWithDynamic(out x); //Ran from TryFoo(string)
g.TryFoo(out x); //Ran from TryFoo(string)
}
public class GenericTest
{
//This would fix it, but in my case, not an option
//public bool TryRunTest(out string withOutput) => return TryFoo(out withOutput);
public bool TryRunTest<T>(out T withOutput)
{
return TryFoo(out withOutput);
}
public bool TryRunTestWithDynamic<T>(out T withOutput)
{
if(typeof(T) == typeof(string))
{
var retval = TryFoo(out string s);
withOutput = (T)(dynamic)s;
return retval;
}
return TryFoo(out withOutput);
}
public bool TryFoo<T>(out T withOutput)
{
withOutput = default(T);
Console.WriteLine("Ran from TryFoo<T>");
return true;
}
public bool TryFoo(out string withOutput)
{
withOutput = "Strings are special";
Console.WriteLine("Ran from TryFoo(string)");
return true;
}
}
You can see that TryRunTestWithDynamic
has to look for the concrete string
type specifically. I can't figure out a way to do it so that I can just add overloads and then use dynamic resolution to select the overload I want without having to spell it all out (and let's face it--spelling it all out kind of kills the whole point of having overloads in the first place).
In his post I linked to above (and here for good measure), Jon Skeet mentions using MethodInfo.MakeGenericMethod
as an alternative. Here he talks about how to use it. I'm curious if that would help me here, but I can't figure out how to use it with out
parameters.
So, here are my specific questions:
- While, yes this DOES work, it is VERY clunky. Is there a better way to do this? Most importantly, is there a way I can consume overloads that wouldn't require me checking each type specifically?
- Is there a way to use the
MethodInfo.MakeGenericMethod
route usingout
parameters and would that help me? - Let's say we could wave a magic wand and all of a sudden a language feature was added to C# that would give us the option to force dynamic resolution for generic methods (either for all cases, or only for the case where the generic parameter is for an
out
, like my main problem). Taking from the above examples, let's say we had something like this:
//The following are all invalid syntax but illustrate possible
// ways we may express that we want to force dynamic resolution
//For out parameters
public void TryFoo<T>(dynamic out T withOutput){...}
//"dynamic out" could be a special combination of keywords so that this only works
// with output variables. Not sure if that constraint buys us anything.
// Maybe we would want to allow it for regular (not out) generic parameters as well
public void Foo<T>(dynamic T withInput){...}
public void Foo<dynamic T>(T withInput){...}
public void dynamic Foo<T>(T withInput){...}
Let's not focus on the syntax itself; we could easily use a different notation--that isn't the point. The idea is simply that we are telling the runtime that when we get to TryFoo<T>...
pause and check to see if there is a different, better fitting overload that we should switch to instead. I'm also not particular on whether we are checking for a better resolution on T
specifically, or if it applies to the method in general.
Is there any reason why this might be a BAD thing? As he often does, Eric Lippert makes some interesting points about generic and concrete overloading resolution when inheritance is involved (Jon Skeet is also quoted in other answers, as well) and discusses why choosing the most specific overload isn't always the best choice. I'm wondering if having such a language construct might introduce similar problems.