C#:为什么传递null会导致Object[]重载(但仅在某些情况下)
本文关键字:重载 情况下 为什么 null Object | 更新日期: 2023-09-27 18:27:23
取消对下面标记行的注释将导致stackoverflow,因为重载解决方案有利于第二种方法。但在第二种方法的循环中,代码路径会占用第一个重载。
这是怎么回事?
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
的调用中,编译器必须使用算法(这里是普通人希望理解的语言的答案)来推断您想要的重载,在这种情况下,该算法会得出错误的答案。
在这两者中,它选择Object[]
,因为Object[]
可以强制转换为Object
,但事实并非如此——Object[]
"更具体",或更专业。它离类型层次结构的根更远(或者在通俗英语中,在这种情况下,其中一个类型是Object
,另一个不是)。
为什么专业化是标准?假设给定两个相同名称的方法,具有更通用参数类型的方法将是一般情况(您可以将anything强制转换为Object
),对于某些特定的情况,带有更靠近类型层次结构叶子的类型的重载将取代一般的case方法:"除非它是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
会把重载解析搞得一团糟,以至于我有时会想,让编译器来计算它们是否不是语言设计者的错误(注意:"我有时会怀疑它是否……"不是教条式的确定性表达;设计语言的人比我更聪明)。
编译器尽可能地聪明,这"不是很聪明"。它可能有零星的彻头彻尾的恶意,但这起案件只是善意出了问题。
重载时,如果遇到歧义,编译器将始终尝试执行最具体的方法。
在这种情况下,object[]
比object
更具体。
null
可以是任何类型,因此它匹配两个方法签名。由于编译器必须做出决定,它将选择Test(string name, Object[] val)
,从而导致StackOverflowException
。
然而,在foreach循环中,v
被推断为object
类型。请注意,现在您有了一个类型化变量。
作为object
,v
可以是object
或object[]
(或几乎任何类型),但编译器不知道这一点,至少在运行时才会知道。
重载是在编译时解决的,因此编译器唯一的线索是v
是object
,因此它将选择调用Test(string name, Object value);
如果你有以下行:
var val = new object[] { };
Test(name, val);
那么就会调用Test(string name, Object[] val)
,因为编译器在编译时知道val
是object[]
。
在对Test(name, v);
的调用中,您的变量v
作为与其关联的Object
的类型,即使它的值是null
,我们仍然知道我们有"什么类型的null"。
在对Test(name, null);
的调用中,null没有与其关联的类型,因此编译器会找到最接近的匹配项并使用该重载。最接近的匹配是object[]
过载。