SUMTEC -- There's a thing in my bloglet.

But it's not only one. It's many. It's the same as other things but it exactly likes nothing else...

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  207 随笔 :: 19 文章 :: 1637 评论 :: 12 Trackbacks

 

delegate void MyInvoker();

void Test(MyInvoker Invoker,  int SomeOthers)
{
 //... codes here
}

void Test(object DefaultValue, int SomeOthers)
{
 //... codes here
}

Test(null, 1);


对于这样一个代码,大家说说看会调用哪一个函数?答案是delegate的版本。这个跟函数出现的顺序无关,而跟调用重载函数的猜测规则有关。这个规则要求在匹配的情况下,尽可能的使用更为特殊的版本。而对于null来说,任何的class都是适合的(不包括valuetype和struct),而delegate是从object派生出来的,因此编译器就选择了delegate的版本。那好,如果我要用null作为参数调用object版本怎么办?办法有两个:
1、在Test(MyInvoker ...) 函数里面判断Invoker==null,跳转Test(object...)版本。
2、Test( (object) null, ...);

实际上第一个办法里面的跳转,还是要依赖于第二个办法,只是不需要每一个调用都这么写。但是带来了方便的同时,也带来了损失性能的后果,尽管并不是很严重。如果你认为性能非常重要,甚至连这么一句判断都是不可容忍的,同时也不希望每次调用都要多写一个(object)来限定调用的版本,那么我建议你不要用重载,直接把这个版本的函数换一个名字,例如TestObject(object DefaultValue, ...)。

到这里事情还没有结束,我们再添加一个函数:

void Test(Label DefaultValue, int SomeOthers)
{
 //... codes here
}


然后我们仍然使用Test(null, 1)来测试,结果却发现编译错误,对吧?因为Label和delegate之间不是派生和继承关系,而是类似兄弟或者叔叔之类的关系。同样的,如果你有一个Label和ToolBar的版本,编译也是通不过的,这个时候你只好添加一个(Label)之类的强制转换来指定调用的版本。不过实际上也只有null才会有如此麻烦的问题,而对于其它的任何东西都没有这个问题。原因很简单,只有null才是任何引用类型都能够接受的,同时也不知道属于哪一个类型的。有人就会想到另外一个情况了:

object o = null;
Test(o, ...);

假设现在有object、Label、ToolBar三个版本,会不会编译错误呢?答案是不会的,并且你会发现它调用了object的版本。原因是版本判断规则并不关心变量里面的实际内容是什么,只关心这个变量声明成什么类型了。由于这里的o是声明为object,因此自然调用的是object版本的函数,这个和(object)强制转换没有什么区别。也许你就会想到本文最前面提到的两个解决方法,现在这个就应该是第三个。没错,这也是一种办法,但是它既没有第一种方法的方便,同时也没有第二种方法的效率,可谓集百害于一体,所以我就没有这么建议。(噢,当然,还有一种办法就是调整参数的顺序,或者添加一个没有意义的valuetype参数,使得通过参数的位置和排列就能够明显的确定调用的版本是哪一个了。这个方法同样不是很好,因为可能让你的程序看起来不是那么优雅。)

为了检验一下你是否明白了整个规则,我给出另外一个例子。这个例子仍然假设有object、Label、ToolBar三个版本:

Control c = new Label();
Test(c, ...);

答案还是调用了object版本。

为什么我会提出来这个问题呢?因为最近我就遇到了这种情况。在我的程序里面使用的是一个delegate和一个object的版本,我这里用伪代码写一下:

delegate object MyInvoker();

object Test(MyInvoker Invoker,  int Key)
{
  if (HashTable.Contains(Key))
    return HashTable[Key];
  else
  {
    object Result = Invoker();
    HashTable.Add(Key, Result);
    return Result;
  }
}


void Test(object DefaultValue, int Key)
{
  if (HashTable.Contains(Key))
   
return HashTable[Key];
 
else
  {
    HashTable.Add(Key, DefaultValue);
   
return DefaultValue;
  }

}

Test(null, 1);

很明显,我这里是为了缓冲某个数据——如果某个Key所对应的数据不存在,那么要么通过Invoker来返回一个新的实力,要么通过DefaultValue来设置。如果我调用Test(null, 1),那么应该表示如果找不到的话,就设置成null,而不是调用Invoker=null——这明显是非法的,并且没有意义。最后权衡了一下,我还是不用重载的方式,换了一个名字算了。

至于说提出这个问题还有别的原因的,因为有的时候不是编译错误那么简单。比如上面那个例子就是运行时的问题,并且如果这里不是delegate的问题,而是其它类型,那么可能在调用的时候并不知道调用了错误的版本,而等到很后来才出现问题,这时候要找出这个隐含的bug就不是那么简单了。因此我的结论就是,重载的时候需要考虑一下null的问题,并审慎对待重载。
posted on 2004-06-19 14:02 Sumtec 阅读(799) 评论(3)  编辑 收藏 所属分类: .NET 技术内幕

评论

#1楼  2004-06-19 17:23 hBifTs      
哦,,原来还有这么多的明堂啊..
:)

不过一直以来,除非是有相同的功能的函数,一般都是用不同的函数名来的表示的,很少出现像你说的这种情况:)

  回复  引用  查看    

#2楼  2004-06-19 20:04 dudu      

为什么会调用delegate的版本?那是因为delegate是object的子类。如果delegate不是object的子类,这样的重载是不允许的,编译时就会出错。我认为调用重载函数有一个匹配规则,就是先匹配子类,也就是沿着继承树从下往上进行匹配。下面的代码示例证明了这点:

class TestClass

     {

        

         public class Father

         {

              public Father()

              {

                  

              }

         }

 

         public class Son : Father

         {

              public Son()

              {

                  

              }

         }

 

         public class TestOverload

         {

             

              public TestOverload()

              {

                  

              }

 

              public  void Test(Father father, int SomeOthers)

              {

                   Console.WriteLine("Test Father");

              }

 

              public void Test(Son son,  int SomeOthers)

              {

                   Console.WriteLine("Test Son");

             

              }

         }

 

          [STAThread]

         static void Main(string[] args)

         {

              TestOverload test=new TestOverload();

              test.Test(null,1);

              Father father=new Father();

              Son son=new Son();

              Console.ReadLine();

         }

     }

运行结果: Test Son


  回复  引用  查看    

#3楼  2004-06-22 13:26 Ninputer [未注册用户]
也许Visual Basic更复杂,因为VB认为Nothing可以是任何类型,包括值类型
  回复  引用    


标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      


相关链接: