从一组重载中获取最佳匹配的重载

本文关键字:重载 最佳 获取 一组 | 更新日期: 2023-09-27 17:52:43

假设我有一个类如下:

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],并且因为[stringint=default]比[object]更喜欢。

在我的场景中,我希望使用反射来获得最佳匹配重载。换句话说:"best"被定义为"c#重载解析"。例如:

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

因为我收到一条评论,由于过载解决方案的实施困难(我很清楚(,这对SO来说太难了,所以我倾向于发送更新。为了给我的论点一些力量,这是我的第一次尝试,它使用默认值。NET活页夹处理过载解决方案:

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

这个版本似乎已经正确地解决了简单的过载,但无法使用可选参数。因为NET afaik可以像我在这里展示的那样使用类型绑定,我想这个解决方案可以很容易地实现。

从一组重载中获取最佳匹配的重载

这是一个庞大的主题,需要大量的工作,在我看来肯定无法用SO答案来完成。我建议您通读C#规范,阅读定义重载解决方案的正式规则(此外,请注意通用方法(,并尝试实现它们,直到满足您的需求。

更新

可选(即具有默认值的参数(并不是一个简单的情况,反射绑定器根本不会尝试填充它们,这是因为编译器的工作是识别默认值,提取它们,并将它们注入到对此类方法的调用中。

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

  1. 手动搜索一个方法,该方法的参数数量和参数类型与完全匹配,即您所获得的参数的数量和类型。如果你找到了一根火柴,那就用它吧。

  2. 现在,为您的重载选择确定方法的"候选列表"(通常是通过名称——您也可以在这里排除泛型——除非您也要尝试绑定它们(。

  3. 如果这些方法都没有可选的参数,那么你可以根据你的问题使用默认的绑定器来查找匹配项(如果没有,你需要一个基于类型的参数/参数排序算法-如果是,跳到5(。

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

  5. 对3(和4(中内置的这些方法运行排名算法,以确定最佳匹配(你似乎对此处理得很好,所以我不打算在这里全部介绍——这不是一个简单的算法,坦率地说,我也不能在这里逐字引用!(。

  6. 你的排名算法应该产生一个明确的获胜方法,即获得独特的高分或类似的分数。如果你得到了一个明显的赢家,那么这就是你绑定的那个。否则,你会有歧义,你必须放弃。

在这一点上,您可能对我自己的SO感兴趣——默认参数和反射:if ParameterInfo。IsOptional那么DefaultValue总是可靠的吗?-这将有助于您识别具有默认参数的方法,以及如何将它们移除。

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

重要的一点是,"动态"技巧并不适用于所有场景(特别是:泛型(;编译器似乎比运行时行为更灵活。

还要注意,这不是一个完整的算法/实现(或者至少我认为不是(,但在大多数情况下都有效,包括尽管如此。我发现这适用于迄今为止遇到的所有情况,包括数组协方差等困难情况。

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

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

相容性得分是a型和B型之间最严格的转换(包括和协方差、反方差(。例如,字符串[]有1个到IList的转换(使用对象[],然后使用IList(和2个到IEnumerable的转换(1。通过使用对象[],然后使用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的精彩链接(,并确保在返回方法之前填写泛型参数。如果你遇到了最佳方法的平局:最好的解决方案是抛出一个异常。

如果你想知道,是的。。。要使一切正常工作需要相当多的工作。。。一旦完成,我计划在我的框架中提供一个工作版本。(你会看到我的个人资料有一个工作网站链接的那一刻:-(