Search code examples
c#.netsystem.reflection

How to select a method based on parameter types using reflection?


I have an array of parameters and I need to invoke an instance method that should have the most appropriate signature for the invocation, giving the number and type of parameters on my parameter array. The difficulty I'm having finding an answer for this on similar questions on StackOverflow it's because the method I'm targeting has not any specific name; instead, I must select a method from a list of methods that are decorated with a specific Attribute.

Example of a class containing some methods that can be invoked:

 public class TestClass
    {

        [MyAttribute]
        public void DoStuff()
        {
            // Do something
        }

        [MyAttribute]
        public void DoMoreStuff(string msg)
        {
            // Do something
        }

        [MyAttribute]
        public void DoEvenMoreStuff(string msg, int count, bool isCool = true)
        {
            // Do Something
        }

        [MyAttribute]
        public void DoEvenMoreStuff(object obj, int count, bool isCool = true)
        {
            // Do Something
        }

    }

Now, I need to be able to invoke one of the methods decorated with MyAttribute, but I don't know the name of those methods beforehand. I just need to get all the methods that are decorated with MyAttribute and select one of them based on an array of parameters I already have; and then invoke the selected method.

How should I do to select the best method to be invoked?


Solution

  • You probably want to use Binder.SelectMethod on the default binder (Type.DefaultBinder). This will handle standard implicit conversions (e.g. to base types, interfaces, or object) as well as some numeric conversions (e.g. int to double and long). See the documentation for a more thorough description. This will not match on optional/default parameters. So if you pass string and int it will not match the 3rd method in your example with a default value for the third boolean parameter. Note you may also want to handle the AmbiguousMatchException which is thrown when more than one candidate method could be selected. A short demonstration of its use:

    static void Main(string[] args)
    {
        var methods = typeof(TestClass).GetMethods()
            .Where(mi => mi.GetCustomAttributes(true).OfType<MyAttribute>().Any()).ToArray();
    
        var flags = BindingFlags.Default; // I did not see a difference with BindingFlags.OptionalParamBinding;
    
        Type[][] cases = {
            new Type[0],
            new[] { typeof(string) },
            new[] { typeof(string), typeof(int) },
            new[] { typeof(string), typeof(int), typeof(bool) },
            new[] { typeof(int), typeof(int), typeof(bool) }
        };
    
        foreach (var typeCase in cases)
        {
            string desc = "(" + string.Join(",", typeCase.Select(t => t?.Name ?? "<null>")) + ")";
    
            var method = Type.DefaultBinder.SelectMethod(flags, methods, typeCase, null);
    
            string result = method?.ToString() ?? "No matching method found";
            Console.WriteLine($"{desc} -> {result}");
        }
    }
    

    Output:

    () -> Void DoStuff()
    (String) -> Void DoMoreStuff(System.String)
    (String,Int32) -> No matching method found
    (String,Int32,Boolean) -> Void DoEvenMoreStuff(System.String, Int32, Boolean)
    (Int32,Int32,Boolean) -> Void DoEvenMoreStuff(System.Object, Int32, Boolean)