从一组重载中获取最佳匹配的重载
本文关键字:重载 最佳 获取 一组 | 更新日期: 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
],并且因为[string
,int=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#规范,阅读定义重载解决方案的正式规则(此外,请注意通用方法(,并尝试实现它们,直到满足您的需求。
更新
可选(即具有默认值的参数(并不是一个简单的情况,反射绑定器根本不会尝试填充它们,这是因为编译器的工作是识别默认值,提取它们,并将它们注入到对此类方法的调用中。
你需要一个类似这样的多遍方法(注意-不包括泛型(:
-
手动搜索一个方法,该方法的参数数量和参数类型与完全匹配,即您所获得的参数的数量和类型。如果你找到了一根火柴,那就用它吧。
-
现在,为您的重载选择确定方法的"候选列表"(通常是通过名称——您也可以在这里排除泛型——除非您也要尝试绑定它们(。
-
如果这些方法都没有可选的参数,那么你可以根据你的问题使用默认的绑定器来查找匹配项(如果没有,你需要一个基于类型的参数/参数排序算法-如果是,跳到5(。
-
重新运行3中构建的候选列表(,取出所有可选参数,并将其默认值合并到您自己的参数列表中(此时您可能需要为每个方法构建一组单独的参数,包括已提供的参数,但也包括默认值(。
-
对3(和4(中内置的这些方法运行排名算法,以确定最佳匹配(你似乎对此处理得很好,所以我不打算在这里全部介绍——这不是一个简单的算法,坦率地说,我也不能在这里逐字引用!(。
-
你的排名算法应该产生一个明确的获胜方法,即获得独特的高分或类似的分数。如果你得到了一个明显的赢家,那么这就是你绑定的那个。否则,你会有歧义,你必须放弃。
在这一点上,您可能对我自己的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的精彩链接(,并确保在返回方法之前填写泛型参数。如果你遇到了最佳方法的平局:最好的解决方案是抛出一个异常。
如果你想知道,是的。。。要使一切正常工作需要相当多的工作。。。一旦完成,我计划在我的框架中提供一个工作版本。(你会看到我的个人资料有一个工作网站链接的那一刻:-(