22

When the C# compiler interprets a method invocation it must use (static) argument types to determine which overload is actually being invoked. I want to be able to do this programmatically.

If I have the name of a method (a string), the type that declares it (an instance of System.Type), and a list of argument types I want to be able to call a standard library function and get back a MethodInfo object representing the method the C# compiler would choose to invoke.

For instance if I have

class MyClass {
  public void myFunc(BaseClass bc) {};
  public void myFunc(DerivedClass dc) {};
}

Then I want something like this fictional function GetOverloadedMethod on System.Type

MethodInfo methodToInvoke
  = typeof(MyClass).GetOverloadedMethod("myFunc", new System.Type[] {typeof(BaseClass)});

In this case methodToInvoke should be public void myFunc(BaseClass bc).

NOTE: Neither of the methods GetMethod and GetMethods will serve my purpose. Neither of them do any overload resolution. In the case of GetMethod it only returns exact matches. If you give it more derived arguments it will simply return nothing. Or you might be lucky enough to get an ambiguity exception which provides no useful information.

Fizzy
  • 352
  • 1
  • 2
  • 8
  • I doubt anyone has written a general method of the sort. Let's be clear, you are asking specifically for an implementation of C#'s overload resolution rules? Perhaps Roslyn has something you can use but I doubt it is exposed in way that is convenient for this use case. The rules are spelled out very clearly in section 7.5.3 of the spec if you wish to implement them. – Mike Zboray Apr 15 '15 at 23:32
  • Reimplementing them seemed like a lot of error-prone hard work. Actually I think I'm already discovering my own answer (yes). Although it's not spelled out very well in the documentation, the overloads of GetMethod which involve a Binder look like they might do the job. Until I realized there was a Type.DefaultBinder I had no idea what to do with those overloads. – Fizzy Apr 15 '15 at 23:38
  • The DefaultBinder is what is used by the overloads that do not take a binder. Also I'm not even sure I understand what you are trying to demonstrate with the example, because the default binder should be able to distinguish those two overloads. – Mike Zboray Apr 16 '15 at 00:00
  • @mikez it seems like you're correct. I actually spent a good bit of time researching this question. Embarrassingly I discovered Type.DefaultBinder about 10 minutes after I asked it on SO. The documentation is pretty vague but I'm testing it now and it's working. I just didn't understand the Binder class. Sorry. – Fizzy Apr 16 '15 at 00:07
  • The Binder class is somewhat mysterious, I agree. I was just trying to point out that the resolution done by DefaultBinder might be subtly different than what the C# compiler does. If you're OK with "close enough" then it should work fine. – Mike Zboray Apr 16 '15 at 00:12
  • @mikez: the default binder isn't used at all by those that don't take it. There is no overload resolution applied to `Type.GetMethod(string)`. – Jeroen Vannevel Apr 16 '15 at 00:22
  • 2
    @JeroenVannevel It is used by those that don't take it and have have a types argument e.g. `GetMethod(string, Type[] types)` which is similar to the request method signature. Clearly, there can be no overload resolution when there's no types to resolve. – Mike Zboray Apr 16 '15 at 00:29
  • @mikez: you're right. Though not explicitly mentioned in the documentation, those overloads obviously use it as well. I suppose that solves the problem then.. – Jeroen Vannevel Apr 16 '15 at 00:44

1 Answers1

8

Answer

Use Type.GetMethod(String name, Type[] types) to do programmatic overload resolution. Here is an example:

MethodInfo methodToInvoke = typeof(MyClass)
    .GetMethod("myFunc", new System.Type[] { typeof(BaseClass) });

Explanation

In the example, methodToInvoke will be myFunc(BaseClass bc) not myFunc(DerivedClass dc), because the types array specifies the parameter list of the method to get.

From the MSDN documentation, Type.GetMethod(String name, Type[] types) has two parameters:

  • name is the name of the method to get, and
  • types provides the order, number, and types of the method's parameters.

Running Code

Here is a running fiddle that demonstrates programmatic overload resolution.

using System;
using System.Reflection;

public static class Program
{
    public static void Main()
    {
        MethodInfo methodToInvoke = typeof(MyClass)
            .GetMethod("myFunc", new System.Type[] { typeof(BaseClass) });

        var result = methodToInvoke
            .Invoke(new MyClass(), new object[] { new BaseClass() });

        Console.WriteLine(result);      
    }

    public class MyClass
    {
        public static string myFunc(BaseClass bc) {
            return "BaseClass";
        }

        public static string myFunc(DerivedClass dc) {
            return "DerivedClass";
        }
    }

    public class BaseClass { }
    public class DerivedClass : BaseClass { }
}

The output is BaseClass.

Edit: This is robust.

GetMethod resolved methods that take params, more derived classes, delegates, and interface implementations. This Fiddle demonstrates all of those cases. Here are the calls and what they retrieve.

Works with params

MethodInfo methodToInvoke2 = typeof(MyClass).GetMethod(
        "myFunc",
        new System.Type[] { typeof(Int32[]) });

will resolve this method

public static string myFunc(params int[] i)
{
    return "params";
}

Works with more derived classes

MethodInfo methodToInvoke3 = typeof(MyClass).GetMethod(
    "myFunc", 
    new System.Type[] { typeof(MoreDerivedClass) });

resolves a method that takes the MoreDerivedClass

public class BaseClass { }
public class DerivedClass : BaseClass { }
public class MoreDerivedClass : DerivedClass {}

Works with delegates

MethodInfo methodToInvoke4 = typeof(MyClass).GetMethod(
    "myFunc", 
    new System.Type[] { typeof(MyDelegate) });

... will retrieve a method that takes this delegate:

public delegate void MyDelegate(string x);

Works with interface implementations

MethodInfo methodToInvoke5 = typeof(MyClass).GetMethod(
   "myFunc", 
   new System.Type[] { typeof(MyImplementation) });

... successfully retrieves a method that takes MyImplementation

public interface IMyInterface {}
public class MyImplementation : IMyInterface {}

So, it is robust, and we can use GetMethod to do overload resolution in cases that we might not expect to work.

Shaun Luttin
  • 133,272
  • 81
  • 405
  • 467
  • This is oversimplified. It breaks for at least the following cases: variable arguments (`params`), delegate types (e.g. `delegate void Bla(string x) <=> Action` would not match while it would work for compile time binding), implemented interfaces (same as delegate type equivalence), classes that are derived by more than one level (e.g. `MoreDerivedClass : DerivedClass` would return no match, but should pick `DerivedClass` over `BaseClass`). – Bas Apr 16 '15 at 01:59
  • @Bas It works for all cases in the question. A simple question deserves the simplest possible answer. – Shaun Luttin Apr 16 '15 at 02:01
  • Downvoted for that statement, I do not agree that oversimplification, resulting in an incorrect answer is justified because the answer is presented in a simple way. There is no way for you to oversee the consequences for OP when he ignores these cases. – Bas Apr 16 '15 at 02:07
  • No problem. :-) We can agree to disagree. – Shaun Luttin Apr 16 '15 at 02:10
  • @Bas It works for the cases of concern in your comment. See edits. – Shaun Luttin Apr 16 '15 at 03:06
  • 1
    @Bas Could you show us how one could invoke a method that takes an `Action`, passing a `Bla` instead? – IS4 Apr 16 '15 at 10:18
  • 1
    @IllidanS4 It appears I was wrong about the delegate type system, there is **nominal equivalence**, however the argument for `Action` holds for `Action` allowing an `Action` to be passed in its place. – Bas Apr 16 '15 at 16:48
  • @ShaunLuttin this method does not work for `params`. It breaks if you pass in 2 `Int32` values in the `Type[]` instead of one `Int32[]`. Also, none of your other test cases demonstrate the actual overload resolution, they just match the exact types you declare in your methods, although they do work when you change them. – Bas Apr 16 '15 at 16:54
  • @Bas Yeah, it can't handle contravariance. Maybe checking the dynamic binder would prove useful. – IS4 Apr 16 '15 at 16:56
  • 1
    @IllidanS4 however, the current answer does handle generic variance to my surprise, I added a case here: https://dotnetfiddle.net/45Q2YT. The params break here: https://dotnetfiddle.net/iqYZaP – Bas Apr 16 '15 at 17:01
  • @Bas Re `params`: Why would I pass two `Int32` values if I were looking for a method that took an `Int32[]` parameter? – Shaun Luttin Apr 16 '15 at 17:01
  • 1
    e.g. `Console.WriteLine('{0}, {1}', 1, 2)` will resolve to `Console.WriteLine(string format, params object[] args)` – Bas Apr 16 '15 at 17:02
  • @Bas Re actual overload resolution: What do mean by that? You said "they just match the exact types you declare in your methods." How is actual overload resolution different than this? – Shaun Luttin Apr 16 '15 at 17:02
  • @ShaunLuttin the trick with correct overload resolution is that calling `MyMethod(new MyImplementation())` resolves to `MyMethod(MyInterface x)`, but for instance if `MyMethod(MyImplementation)` was also declared, it would resolve to the most specific one. – Bas Apr 16 '15 at 17:03
  • @Bas What behavior would you be looking for that's different than the behavior that you see here: https://dotnetfiddle.net/UmS6eg – Shaun Luttin Apr 16 '15 at 17:08