Get the best match overload from multiple 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 use 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 this is 7 is because overload resolution prefers [ IList<object> ] over [ IEnumerable<object> ] and [ object ], but because [ string , int=default ] takes precedence over [ object ] .

In my scenario, I would like to get the best matching overload using reflection. In other words: "best" is defined as "C # overload resolution." For instance:.

 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 sketch I script has only 1 parameter, the solution I'm looking for can have several parameters.

Update 1 :

Since I received a comment that this is too complicated for SO due to difficulties with implementing overload resolution (which I am well aware), I feel apt to send updates. To give my argument of some power, this was my first attempt, which uses the default .NET middleware, which handles 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 make simple overload resolution correct, but does not work with additional parameters. Since .NET afaik works with type binding, as shown here, I believe that the solution can be implemented quite easily.

+4
source share
2 answers

This is a massive subject, it requires a lot of work and, in my opinion, cannot be completed in an SO-response. I suggest you read the C # specification and read the formal rules that define overload resolution (also pay attention to common methods) and try to implement them to the extent that will satisfy your needs.

Update

Optional (i.e., parameters with default values) are not a trivial case - and the Reflection binder does not attempt to populate them - because this is the task of the compiler to determine the default values, pulls them out and introduces such a method.

You need a multi-pass approach, something like this (note - DOES NOT include generics):

  • Manual search for a method whose number of parameters and types matches exactly the number and types of arguments you have. If you find a match, use it and upload.

  • Now specify the "list of candidates" of the methods for selecting overloads (usually by name - you can also exclude generics here, unless you try to link them).

  • If none of these methods has optional parameters, you can continue and use the default binder according to your question to find a match (if not, you need a type-based parameter / parameter determination algorithm - if so, skip to 5).

  • Repeating the candidate list built in 3), pull out all optional parameters and include their default values โ€‹โ€‹in your own parameter lists (you may need to create a separate set of parameters for each method at this point, including those that were provided, but also the values โ€‹โ€‹for default).

  • Run your ranking algorithm for these methods built in 3) and possibly 4) to determine the best match (you seem to have a good handle, so I wonโ€™t go through all this here - this is not a simple algorithm, and I honestly, I canโ€™t bring everything here verbatim!).

  • Your ranking algorithm should give an explicit winning method, i.e. with a unique high score or similar. If you get a clear winner, then the one you are binding. Otherwise, you have ambiguity, and you need to break free.

Perhaps you are interested in my own SO at this point - Default parameters and reflection: if ParameterInfo.IsOptional then is DefaultValue always reliable? - that should help you with the definition of methods that have parameters with default settings, and how to remove them.

+4
source

For other people who want to perform overloading at runtime, this is a fairly complete description of how to implement it.

It is important to note that the โ€œdynamicโ€ trick does not work in all scenarios (in particular: generics); it seems that the compiler is more flexible than runtime behavior.

Also note that this is not a complete algorithm / implementation (or at least I think it is not), but works in most cases, including, however. I found that this works in all cases that I have encountered so far, including complex cases such as covariance of arrays.

The evaluation algorithm works as follows:

  • If parameter type == Source type: score = 0
  • If the parameter is an argument of a general type that is valid (general restrictions): score = 1
  • If the source type is implicitly converted to the parameter type: score = 2 (see http://msdn.microsoft.com/en-us/library/y5b434w4.aspx for all rules)
  • If you need to fill in the default parameter: score = 3
  • Otherwise, calculate the score to evaluate compatibility below

The compatibility assessment is the most stringent conversion of type A and type B (including covariance, contravariance). For example, the string [] has 1 conversion to IList (using the object [] and then IList) and 2 conversions to IEnumerable (1. using the object [] and then IEnumerable or 2. IEnumerable). Therefore, IList is a more rigorous conversion and therefore selected.

Counting conversions is easy:

  int cnt = CountCompatible(parameter.ParameterType, sourceType.GetInterfaces()) + CountCompatible(parameter.ParameterType, sourceType.GetBaseTypes()) + CountCompatible(parameter.ParameterType, new Type[] { sourceType }); [...] private static int CountCompatible(Type dst, IEnumerable<Type> types) { int cnt = 0; foreach (var t in types) { if (dst.IsAssignableFrom(t)) { ++cnt; } } return cnt; } 

To make sure that with a more rigorous conversion, a better score is selected, I calculate the score as 'score = 5 - 1.0 / (cnt + 2);'. +2 ensures that you never divide by 0 or 1, which will result in a score of 4 to 5.

To enable overload resolution, select the method with the lowest score for all arguments. Make sure that you correctly enter the arguments to the method method when calling (see Andras' s excellent link above) and make sure you fill in the general arguments before returning the method. If you meet a draw for a better method: the best resolution is throwing an exception.

In case you are wondering, yes ... it is quite a lot of work so that it all works correctly ... I plan to make a working version available in my environment as soon as it is done. (You will see the moment when my profile has a working site link :-))

+2
source

All Articles