C#:为什么传递null会接受对象[]的重载(但仅在某些情况下)?
取消对下面标记行的注释将导致stackoverflow,因为重载解析有利于第二种方法。但是在第二个方法的循环中,代码路径使用第一个重载 这是怎么回事C#:为什么传递null会接受对象[]的重载(但仅在某些情况下)?,c#,C#,取消对下面标记行的注释将导致stackoverflow,因为重载解析有利于第二种方法。但是在第二个方法的循环中,代码路径使用第一个重载 这是怎么回事 private static void Main(string[] args) { var items = new Object[] { null }; Test("test", items); Console.ReadKey(true); } public static void Test(String name,
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
,另一个不是)
为什么专业化是标准?假设给定两个名称相同的方法,具有更一般参数类型的方法将作为一般情况(您可以将任何内容强制转换为对象
),并且对于某些特定情况,具有更靠近类型层次结构叶子的类型的重载将取代常规情况方法:“除非它是一个对象数组,否则在任何情况下都要坚持使用此项;我需要为对象数组做一些不同的事情”
这不是唯一可以想象的标准,但我认为其他任何一半都不如这一半好
在这种情况下,它是违反直觉的,因为你认为null
是一个尽可能普遍的东西:它甚至不是具体的对象
。它是……随便什么
下面的方法是可行的,因为在这里,编译器不必猜测您所说的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
可以是任何类型,因此它匹配两个方法签名。由于编译器必须做出决定,它将选择测试(字符串名称,对象[]val)
导致堆栈溢出异常
然而,在foreach循环中,v
被推断为对象类型。请注意,现在您有了一个类型化变量
作为对象
,v
可以是对象
或对象[]
(或几乎任何类型),但编译器不知道这一点,至少在运行时之前不知道
重载是在编译时解决的,因此编译器的唯一线索是v
是一个对象
,因此它将选择调用测试(字符串名称,对象值)代码>
如果您有以下行:
var val = new object[] { };
Test(name, val);
然后调用Test(字符串名,Object[]val)
,因为编译器知道val
在编译时是一个Object[]
。在调用Test(name,v)时
您的变量v
作为与其关联的对象的一种类型,即使它的值是null
我们仍然知道“什么类型的null”
在调用Test(name,null)时
该null没有与之关联的类型,因此编译器查找并使用该重载。最近的匹配是对象[]
重载。我确实想到了这一点,但是为什么重载不一致呢?这意味着注释行与循环中的内容有何不同?在循环中,v
是一个对象,因此它与第一个重载完全匹配。@JamesThorpe为什么要在可以回答并获取repz时复制关闭@JamesThorpe很确定Bjom的评论是讽刺性的…@Servy是的,我现在明白了。。。虽然我的评论可以更好地针对下面的其他人:)很好的链接。老实说,我没料到编译器会那么聪明。@lonimore您认为它不够聪明,无法将所使用的表达式类型与所调用方法的重载参数列表进行比较吗?除了在编译时几乎什么都不做,在运行时绑定所有内容之外,真的没有其他选择了。@Servy我理解它用于方法重载的规则。我只是不这么想