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

时间:2013-01-14 08:56:11

标签: c# .net overload-resolution

假设我有一个课程如下:

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

现在,如果我尝试在代码中使用它,我会使用以下内容:

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

它是7的原因是因为重载决议比[IList<object>]和[IEnumerable<object>]更喜欢[object],因为[string,{{1 }}优先于[int=default]。

在我的场景中,我想使用反射获得最佳匹配重载。换句话说:'best'被定义为'c#overload resolution'。 E.g:

object

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

更新1

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

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

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

2 个答案:

答案 0 :(得分:4)

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

更新

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

你需要一种类似这样的多遍方法(注意 - 不包含泛型):

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

  2. 现在确定候选人名单&#39;过载选择的方法(通常按名称排序) - 你也可以在这里排除泛型 - 除非你也尝试绑定它们。)

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

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

  5. 为3)中可能构建的这些方法运行您的排名算法,可能还有4)确定最佳匹配(您似乎对此有了很好的处理,所以我不打算在这里完成所有操作 - 这不是一个简单的算法,我坦白地说,不能在这里逐字引用!)。

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

  7. 此时您可能对我自己的SO感兴趣 - Default parameters and reflection: if ParameterInfo.IsOptional then is DefaultValue always reliable? - 这可以帮助您识别具有默认参数的方法,以及如何解除它们。

答案 1 :(得分:2)

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

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

另请注意,这不是一个完整的算法/实现(或者至少我认为它不是),但在大多数情况下仍然有效。我发现这在我到目前为止遇到的所有情况下都有效,包括阵列协方差等困难情况。

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

  • 如果参数类型==源类型:得分= 0
  • 如果参数是有效的泛型类型参数(泛型约束):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<Type> 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的优秀链接),并确保在返回方法之前填写泛型参数。如果您遇到最佳方法的绘制:最佳解决方案是抛出异常。

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