Search code examples
c#.netoverload-resolution

Get best matching overload from set of overloads


Let's say I have a class as follows:

public class AcceptMethods
{
    public int Accept(string s, int k = 1)
    {
        return 1;
    }

    public int Accept(object s)
    {
        return 2;
    }

    public int Accept(IEnumerable<object> s)
    {
        return 7;
    }
    public int Accept(IList<object> s)
    {
        return 4;
    }
}

Now, if I try to consume this in code, I use something like this:

        object[] list = new object[] { "a", new object[0], "c", "d" };
        Assert.AreEqual(7, list.Select((a)=>((int)new AcceptMethods().Accept((dynamic)a))).Sum());

The reason that it's 7, is because overload resolution prefers [IList<object>] over [IEnumerable<object>] and [object], and because [string, int=default] has preference over [object].

In my scenario, I'd like to get the best matching overload using reflection. In other words: 'best' is defined as 'c# overload resolution'. E.g.:

int sum = 0;
foreach (var item in list)
{
    var method = GetBestMatching(typeof(AcceptMethods).GetMethods(), item.GetType());
    sum += (int)method.Invoke(myObject, new object[]{item});
}
Assert.AreEqual(7, sum);

While the scenario I sketch has only 1 parameter, the solution I seek can have multiple parameters.

Update 1:

Because I received a comment that this is too difficult for SO due to the difficulties of overload resolution implementation (which I am well aware of), I feel inclined to send an update. To give my argument some power, this was my first attempt, which uses the default .NET binder that handles the overload resolution:

    private MethodBase GetBestMatching(IEnumerable<MethodInfo> methods, Type[] parameters)
    {
        return Type.DefaultBinder.SelectMethod(BindingFlags.Instance | BindingFlags.Public | BindingFlags.OptionalParamBinding | BindingFlags.InvokeMethod,
                        methods.ToArray(), parameters, null);
    }

This version already seems to do simple overload resolution correctly, but fails to work with optional parameters. Because .NET afaik works with type binding like I show here, I suppose that the solution could be implemented fairly easy.


Solution

  • This is a massive subject, requires quite a lot of work and certainly cannot be wrapped up in an SO answer in my opinion. I suggest you read through the C# spec and read the formal rules defining overload resolution (also, please pay attention to generic methods) and try to implement them up to the point that satisfies your needs.

    Update

    Optional (i.e. parameters with default values) are not a trivial case - and the Reflection binder makes no attempt at all to fill them in - that's because it's a compiler's job to identify the default values, pull them out and inject them into a call to such a method.

    You need a multi-pass approach that's something like this (note - does NOT include generics):

    1. Search manually for a method whose number of parameters and types of those parameters match exactly the number and types of arguments you've got. If you find a match - use it and bunk out.

    2. Now identify the 'candidate list' of methods for your overload selection (generally that's by name - you might also exclude generics here - unless you're going to try and bind those too).

    3. If none of those methods have optional parameters then you might be able to go ahead and use the default binder as per your question to find the match (if not, you need a argument/parameter-ranking algorithm based on the types - if so, skip to 5).

    4. Re-running through the candidate list built in 3), pull out all the optional parameters and incorporate their default values into your own parameter lists (you might need to build a separate set of parameters for each method at this point, including those that you have been supplied, but also the defaults).

    5. Run your ranking algorithm for these methods built in 3) and possibly 4) to identify the best match (you seem to have a good handle on this so I'm not going to go through it all here - it's not a simple algorithm and I frankly can't quote it all verbatim here either!).

    6. Your ranking algorithm should produce a clear winning method - i.e. with a unique high score or similar. If you get a clear winner, then that's the one you bind. Otherwise you have an ambiguity and you have to bunk out.

    You might be interested in my own SO at this point - Default parameters and reflection: if ParameterInfo.IsOptional then is DefaultValue always reliable? - which should help you with identifying methods that have parameters with defaults, and how to lift them out.