取消注释下面标记的行将导致堆栈溢出,因为重载解析有利于第二种方法.但是在第二种方法的循环中,代码路径采用第一次重载.
这里发生了什么?
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);
}
}
Run Code Online (Sandbox Code Playgroud)
第二个调用如您所期望的那样工作,因为val在第二个方法中,其类型为Object[],因此在 中foreach,var v很容易推断为类型Object。那里没有歧义。
第二个调用是不明确的:类型Object和的引用Object[]都可以是null,因此编译器必须猜测您指的是哪一个(更多信息见下文)。null没有任何自己的类型;如果确实如此,您将需要显式强制转换来对其执行几乎任何操作,这将是令人不快的。
重载解析发生在编译时,而不是运行时。循环中的重载决策不是基于是否有时v发生null;而是基于是否发生。直到运行时(编译器解决重载很久之后)才会知道这一点。它基于 的声明类型v。的声明类型v是推断出来的,而不是显式声明的,但要点是它在编译时、解析重载时是已知的。
在您显式传递的另一个调用中null,编译器必须使用算法推断您想要哪个重载(这是普通人希望理解的语言的答案),在这种情况下会得出错误的答案。
在两者中,它选择是Object[]因为Object[]可以强制转换为Object,但反之则不然——Object[]是“更具体”,或更专业。它距离类型层次结构的根更远(或者用简单的英语来说,在这种情况下,其中一种类型是,Object另一种不是)。
为什么专业化是标准?假设是,给定两个同名的方法,具有更通用参数类型的方法旨在作为一般情况(您可以将任何内容强制转换为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);
}
}
Run Code Online (Sandbox Code Playgroud)
简短回答:明确nulls使重载解析变得一团糟,以至于我有时想知道语言设计者让编译器甚至试图弄清楚它们是否可能不是一个错误(注意“我有时想知道是否它可能......”并不是教条式确定性的表达;设计该语言的人比我聪明)。
编译器已经尽可能智能了,但“不是很智能”。它可能偶尔会出现明显的恶意,但这个案例只是善意却出了问题。