从一组重载中获得最佳匹配过载

假设我有一个课程如下:

public class AcceptMethods { public int Accept(string s, int k = 1) { return 1; } public int Accept(object s) { return 2; } public int Accept(IEnumerable s) { return 7; } public int Accept(IList s) { return 4; } } 

现在,如果我尝试在代码中使用它,我使用这样的东西:

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

之所以它是7,是因为重载分辨率比[ IEnumerable ]和[ object ]更喜欢[ IList ],并且因为[ stringint=default ]优先于[ object ]。

在我的场景中,我想使用reflection获得最佳匹配重载。 换句话说:’best’被定义为’c#overload resolution’。 例如:

 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); 

虽然我绘制的场景只有1个参数,但我寻求的解决方案可以有多个参数。

更新1

因为由于重载决策实现的困难(我很清楚),我收到的评论认为这对于SO来说太难了,所以我倾向于发送更新。 为了给我的论证一些力量,这是我的第一次尝试,它使用处理重载决策的默认.NET绑定器:

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

此版本似乎已正确执行简单的重载解析 ,但无法使用可选参数。 因为.NET afaik与我在这里展示的类型绑定一起工作,我想这个解决方案可以很容易地实现。

这是一个庞大的主题,需要相当多的工作,在我看来当然不能用SO答案包装。 我建议你阅读C#规范并阅读定义重载决策的正式规则(另外,请注意通用方法)并尝试实现它们以满足您的需求。

更新

可选(即具有默认值的参数)不是一个简单的例子 – 并且Reflection绑定器根本不会尝试填充它们 – 这是因为编译器的工作是识别默认值,将它们拉出并将它们注入到调用这样的方法。

你需要一个像这样的多遍方法(注意 – 不包括generics):

  1. 手动搜索一个方法,该方法的参数数量和参数类型与您获得的参数的数量和类型完全匹配。 如果你找到一个匹配 – 使用它和bunk out。

  2. 现在确定过载选择的方法的“候选列表”(通常是按名称 – 你也可以在这里排除generics – 除非你也尝试绑定它们)。

  3. 如果这些方法都没有可选参数,那么您可以继续使用默认绑定器来查找匹配项(如果没有,则需要基于类型的参数/参数排序算法 – 如果是这样,跳到5)。

  4. 重新运行3)中内置的候选列表,拉出所有可选参数并将其默认值合并到您自己的参数列表中(此时您可能需要为每个方法构建一组单独的参数,包括那些已经提供,但也是默认值)。

  5. 运行你的排名算法,为3)中建立的这些方法和可能的4)确定最佳匹配(你似乎有一个很好的处理,所以我不打算在这里完成所有 – 这不是一个简单的算法,我坦率地说,不能在这里逐字引用!)。

  6. 您的排名算法应该产生一个明确的获胜方法 – 即具有独特的高分或类似。 如果你得到一个明确的赢家,那就是你绑定的那个。 否则你会有一种模棱两可的态度,你必须把它掏空。

此时您可能对我自己的SO感兴趣 – 默认参数和reflection:如果ParameterInfo.IsOptional那么DefaultValue总是可靠的吗? – 这应该可以帮助您识别具有默认参数的方法,以及如何解除它们。

对于其他想要进行运行时重载解析的人来说,这是一个关于如何实现它的相当完整的描述。

重要的一点是,“动态”技巧在所有场景中都不起作用(特别是:generics); 似乎编译器比运行时行为更灵活。

还要注意,这不是一个完整的算法/实现(或者至少我认为它不是),但在大多数情况下都适用,但仍然如此。 我发现这在我到目前为止遇到的所有情况下都有效,包括像数组协方差这样的困难情况。

评分算法的工作原理如下:

  • 如果参数类型==源类型:得分= 0
  • 如果参数是有效的generics类型参数(generics约束):score = 1
  • 如果源类型可隐式转换为参数类型:score = 2(请参阅: http : //msdn.microsoft.com/en-us/library/y5b434w4.aspx适用于所有规则)
  • 如果您需要填写默认参数:score = 3
  • 否则,计算兼容性得分的得分如下

兼容性分数是类型类型A和类型B(包括和协方差,逆变)之间最严格的转换。 例如,string []有1个转换为IList(使用object []然后是IList)和2个转换为IEnumerable(1.通过使用object []然后IEnumerable或2.通过IEnumerable)。 因此,IList是更严格的转换,因此被选中。

计算转化次数很简单:

  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 types) { int cnt = 0; foreach (var t in types) { if (dst.IsAssignableFrom(t)) { ++cnt; } } return cnt; } 

为了确保在使用更严格的转换时选择更好的分数,我将得分计算为’得分= 5 – 1.0 /(cnt + 2);’。 +2确保您永远不会除以0或1,从而得分在4到5之间。

要执行重载解析,请选择所有参数的最小分数方法。 确保在调用时正确输入默认方法参数(请参阅上面Andras的优秀链接),并确保在返回方法之前填写generics参数。 如果您遇到最佳方法的绘制:最佳解决方案是抛出exception。

如果你想知道,是的……要让它全部正常工作是相当多的工作……我计划在完成之后在我的框架中提供一个可用的版本。 (你会看到我的个人资料有一个工作网站链接的那一刻:-))