取消注释下面标记的行将导致计算器溢出,因为重载决议有利于第二种方法。但是在第二种方法的循环内部,代码路径采用第一个重载。
这是怎么回事?
private static void Main(string[] args) {
var items = new Object[] { null };
Test("test", items);
Console.ReadKey(true);
}
public static void Test(String name, Object val) {
Console.WriteLine(1);
}
public static void Test(String name, Object[] val) {
Console.WriteLine(2);
// Test(name, null); // uncommenting this line will cause a stackoverflow
foreach (var v in val) {
Test(name, v);
}
}
第二个调用如您所愿,因为第二个方法中的 val
是 Object[]
类型,所以在 foreach
中,var v
很容易推断为 Object
类型。那里没有歧义。
第二个调用是不明确的:Object
和 Object[]
类型的引用都可以是 null
,所以编译器必须猜测哪个一个你的意思(更多关于下面)。 null
本身没有任何类型;如果是这样,您将需要一个明确的转换来用它做几乎任何事情,这会很不愉快。
重载解析发生在编译时,而不是运行时。循环中的重载决议有时并不基于 v
是否碰巧为 null
;直到运行时才知道,在编译器解决重载很久之后。它基于 v
的声明类型。 v
的声明类型是推断出来的,而不是显式声明的,但重点是它在编译时已知,当重载被解析时。
在您显式传递 null
的另一个调用中,编译器必须 infer which overload you want using an algorithm ( here's an answer in language which normal people can hope to understand ) 在这种情况下给出了错误的答案。
在两者中,它选择了 Object[]
,因为 Object[]
可以转换为 Object
,但反之则不然 - - Object[]
是“更具体的”,或更专业的。它离类型层次结构的根更远(或者用简单的英语来说,在这种情况下,一种类型是 Object
而另一种不是)。
为什么特化是标准?假设给定两个同名方法,具有更通用参数类型的方法是一般情况(您可以将 anything 转换为 Object
),并且具有更靠近类型层次结构叶子的类型的重载将旨在取代某些特定情况下的一般情况方法:“除非它是 Object
的数组,否则一切都坚持使用这个方法;我需要为对象数组做一些不同的事情”。
这不是唯一可以想象的标准,但我想不出任何其他标准。
在这种情况下,它是违反直觉的,因为您认为 null
是尽可能普遍的事物:它甚至不是特定的 Object
。这是...随便。
下面的代码会起作用,因为在这里编译器不必猜测 null
是什么意思:
public static void Test(String name, Object[] val) {
Console.WriteLine(2);
Object dummy = null;
Test(name, dummy);
foreach (var v in val) {
Test(name, v);
}
}
简短的回答:显式的 nulls
使重载解析变得一团糟,以至于我有时怀疑是否语言设计者让编译器尝试是否是错误的弄清楚它们(注意“我有时想知道它是否可能......”不是教条确定性的表达;设计语言的人比我聪明)。
编译器已经尽可能聪明了,但“不是很聪明”。它可能有零星的完全恶意,但这种情况只是善意出错了。