2

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:

  1. RunTest is a generic method that calls another generic method, Foo. When I call RunTest, it appears the compiler doesn't follow all the way from the call site to see that g.RunTeset is passing in x which is a string and link it all up so that the Foo(string) overload is called; instead, it just sees that RunTest<T> is calling Foo. It doesn't make different "paths" based on different input values of T. OK, that's fair and understandable.
  2. If I call Foo directly from my Main method, the compiler is smart enough to see that we are calling Foo with a string directly and correctly selects the concrete overload.
  3. 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.
  4. I've commented out a line: a concrete overload of RunTest. This would be essentially the same as calling Foo 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:

  1. 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?
  2. Is there a way to use the MethodInfo.MakeGenericMethod route using out parameters and would that help me?
  3. 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.

David
  • 4,665
  • 4
  • 34
  • 60
  • What is the actual use case here, its hard to see why you are in this situation. – TheGeneral Jul 11 '20 at 00:51
  • Note that _all_ overloads are resolved statically unless you use `dynamic`, not just generic ones. Static binding is the default in C#, and this is one of its design goals. Introducing more dynamic into mix will make C# less C#-ish. There isn't anything wrong with what you are proposing per se. Dynamically-typed languages do that all the time. But something like that just doesn't "fit in" in C#. – Sweeper Jul 11 '20 at 01:08
  • @TheGeneral: I have a legacy code base that makes heavy use of several flavors of my version of `TryRunTest(out T output)`. If I can change `TryFoo`, then I can add some valuable functionality without having to refactor a ton of code. My hack works and if I can't find something more elegant, I plan to stick with it, but it just got me thinking - I can't be the only person to have faced this problem, so is there a better way? – David Jul 12 '20 at 04:31

1 Answers1

0

OK, so I proposed the idea of adding support for opting-in to dynamic binding at the C# GitHub and was given this alternate solution which I like MUCH better than what I am currently doing: ((dynamic)this).TryFoo(out withOutput);

It would look like this:

public bool TryRunTestWithDynamic<T>(out T withOutput)
{
    return ((dynamic)this).TryFoo(out withOutput);
}

I will reiterate the warning I was given: this only works for instance methods. In my case, that's fine, but if someone else had a method that was static, it wouldn't help, so the second part of my question (regarding MethodInfo.MakeGenericMethod) may still be relevant.

David
  • 4,665
  • 4
  • 34
  • 60