5

假设我有一堂课如下:

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, int=default] 优先于 [ object]。

在我的场景中,我想使用反射获得最佳匹配的重载。换句话说:“最佳”被定义为“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 个参数,但我寻求的解决方案可以有多个参数。

更新 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 与我在这里展示的类型绑定一起工作,所以我认为该解决方案可以相当容易地实现。

4

2 回答 2

4

这是一个庞大的主题,需要大量的工作,在我看来,当然不能包含在一个 SO 答案中。我建议您通读 C# 规范并阅读定义重载解析的正式规则(另外,请注意泛型方法)并尝试将它们实现到满足您需求的程度。

更新

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

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

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

  2. 现在为您的重载选择确定方法的“候选列表”(通常是按名称 - 您也可以在这里排除泛型 - 除非您也打算尝试绑定它们)。

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

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

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

  6. 你的排名算法应该产生一个明确的获胜方法——即具有独特的高分或类似的。如果你得到一个明显的赢家,那么这就是你绑定的那个。否则你有一个模棱两可的,你必须铺开。

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

于 2013-01-14T09:00:48.557 回答
2

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

重要的附带说明是“动态”技巧也不适用于所有场景(特别是:泛型);似乎编译器比运行时行为更灵活。

另请注意,这不是一个完整的算法/实现(或者至少我认为不是),但在大多数情况下都有效,包括不过。我发现这在我迄今为止遇到的所有情况下都有效,包括像数组协方差这样的困难情况。

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

  • 如果参数类型 == 源类型:分数 = 0
  • 如果参数是有效的泛型类型参数(泛型约束):score = 1
  • 如果源类型可隐式转换为参数类型:分数 = 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;
    }

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

To do overload resolution, select the method with the minimum score for all arguments. Make sure you enter default method arguments properly when invoking (see the excellent link by Andras above) and make sure you fill in the generic arguments before you return the method. If you encounter a draw for the best method: the best resolution is to throw an exception.

In case you wonder, yes... it's quite a bit of work to get it all working correctly... I plan to make a working version available in my framework once that's done. (You'll see the moment my profile has a working website link :-) )

于 2013-01-15T11:17:07.707 回答