翻译:重载解决和Null

原文:http://msmvps.com/blogs/Senthil/
Overload resolution and null
最近在努力学习英语,所以决定自己来翻译一些c#和.NET相关的技术文章,既可学习英语,又能学习到c#和.NET的技术,可谓是一举两得的事情。如果翻译不到位,请谅解,也希望能得到大家友善的指正。

我的同事Soundar发现一个相当有趣的行为
 1: class Test
  2: {
  3:     public static void Main()
  4:     {
  5:         Test test = null;
  6:
  7:         Console.WriteLine("{0}", test);
  8:         Console.WriteLine("{0}", null);
  9:     }
 10: }

如果你运行上面代码,你就会发现第7行打印了一空行,而第8行导致了一个ArgumentNullException异常。注意test的引用也是null,所以这会令你惊讶,因为运行时这两行不一样的行为。

它当然也让我惊讶并使我非得深究导致区别的原因不可。我认为相同的参数值在同样的运行时,产生了不同的结果,是因为编译器的操作——可能是方法重载。果然,第7行和第8行要求不同的重载来解决。

第7行使用了下面重载
public static void WriteLine(string format, object arg0);
第8行使用了下面重载
public static void WriteLine(string format, params object[] arg);

使用Reflector查看IL代码显示当arg为null时第二种重载会抛出异常,而第一种重载会把arg0转变成一个对象数组,并要求第二种重载。

然而,为什么编译器会现在两种不同的重载呢?
直观地,当只传入一个参数给调用方法时,你期望重载机制会更倾向于选择单个参数的重载,而不是可变参数的重载。这就是编译器对第七行选择重载一的原因。
在第八行的情况不一样,null直接分配给参数arg0和arg,重载机制必须选择最好的函数,这个时候它选择了参数为对象数组的重载方法。
这显得和直观相反,直到你拥有类似下面的代码:
1: class Test
  2: {
  3:     public static void Main()
  4:     {
  5:         SubTest subTest = null;
  6:         M(subTest);
  7:     }
  8:
  9:     static void M(Test t) { Console.WriteLine("Test"); }
 10:     static void M(SubTest s) { Console.WriteLine("SubTest"); }
 11: }
 12:
 13: class SubTest : Test { }
你不会感到惊讶运行到第六行时是要求重载方法M(SubTest),你会吗?
在C#规范的规则里确定最好的匹配是这样说的:
“给出一个从类型S转换为T1的隐形转换C1和一个从类型S转换为T2的隐形转换C2,这两个转换方式中选择哪个最好有下面的几点决定:
(1)如果类型T1和类型T2相同,两种转换性能一样。
(2)如果S是T1类型,则C1是更好的转换。
(3)如果S是T2类型,则C2是更好的转换。
(4)如果存在类型T1向类型T2隐性转换,并且不存在T2向T1的隐形转换,那么C1是更好的转换方法。
(5)如果存在类型T2向类型T1隐性转换,并且不存在T1向T2的隐形转换,那么C2是更好的转换方法。
....”
在上面代码中,SubTest(T1)可以隐性地转换为Test(T2),所以编译器选择了M(SubTest).
在我们的代码中,编译器努力在null转换为对象object和null转换为数组object[]中选择最好的转换方式,根据上面的转换原则,object[]能隐性地转换为object,所以重载机制选择了WriteLine(string format, params object[] arg). 在解决null参数的案例中,修饰的参数并没有发挥作用。
非常有趣?是不是呢?

 

posted on 2009-08-12 12:33  边写边唱  阅读(340)  评论(0编辑  收藏  举报

导航