posts - 254, comments - 1230, trackbacks - 18, articles - 8
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

        一直以来,我都觉得C#的继承体系结构太过繁琐,既要声明方法是否为virtual,又要使用new、override这样的关键字界定派生类方法在继承体系中的角色,远不如Java的继承实现来得简洁清爽。在Java当中,所有的类方法在默认情况下都是virtual的,所以就省下了将方法声明为virtual这个步骤。也许你会问,如果想声明一个非virtual的方法怎么办呢?所谓非virtual就是不允许继承了,那么在方法的签名中使用final关键字即可。从继承实现的最基本的虚方法声明中,我们可以看出两种语言在设计上不同的关注点:Java会认为基类方法在大部分情况会被覆写(override),而C#则相反。

        尽管C#的继承体系结构显得有些臃肿,但是,我也坚信存在即是真理,C#采用这样的方式实现继承也必然出于其它方面的考虑,至少当我知道C#是这样实现继承的时候,“严谨”一词就在我的脑海中不断闪现。不过,光是“严谨”这种感性的认识还不足以让我对C#的继承实现感到心悦诚服,毕竟作为程序员的我喜欢纯粹,也喜欢简约。于是乎,对C#继承体系结构的偏见就根深蒂固地扎根于我的认知土壤中了。直到最近在开发中碰到了一个问题,解决的过程让我明白到严谨是很重要的,不仅是程序员的态度,还有编程语言本身的支持。
      
        遇到的问题其实很简单,就是派生子类覆写了父类的一个方法,而该父类方法的参数发生了改变,编译器在编译过程中并没有发现这个问题。在程序运行的时候,则出现了死循环,并且没有任何的错误提示。这个bug着实让人很迷惑,也耗费了同事很多的时间,最后才发现是由于父类方法发生了改变,而子类中的覆写方法又没有更新造成的。请参见以下简化的代码:

public class ParentClass {
    
public int test(int i) {          
        
return i * 2;
    }

}


public class DerivedClass extends ParentClass {
    
public int test(int i) {
        
return i * 2 + 1;
    }

}


public class Test {
    
public static void main(String[] args) {
        ParentClass a 
= new DerivedClass();
        System.out.println(a.test(
0));    //输出结果为1
    }

}


接着,ParentClass的代码改为:

public class ParentClass {
    
public int test(int i, boolean flag) {          
        
if (flag) 
            
return i * 2;
        
else
            
return 0
    }

}


通过IDE的支持,我们可以找到使用test方法的地方,也就将main函数中的调用改为:System.out.println(a.test(0, true)); 虽然这样的改动并不会引发编译错误,在运行的时候也没有问题,但是很明显,输出结果变为了0,程序已经不再按照我们的意图而执行了,因为调用的是从父类继承过来的test方法,而不是我们原来所要求的调用覆写的方法了。像这样的bug是很难被发现的,如果不是因为程序在运行过程中出现了死循环,都不知道这个潜在的bug会在什么时候突然爆发。

        以上给出的例子已经很好的说明了C#使用override关键字的必要性。对应着C#,以上代码的DerivedClass中的test方法就要被声明为override,一旦ParentClass中的test方法发生了改变,编译器就会在编译过程发现这个错误。可见语言定义的严谨还是能够在很大程度上帮助程序员在开发中少犯错的。事实上,Java也意识到了这个方面的不足,也在语言定义上作了改进:通过使用内建的Override注释(Annotation)来避免以上提到的问题(参见参考文章[1])。个人觉得,Java的解决方案似乎更加优美,因为它是通过提供一个机制实现了继承上的制约,而非增加关键字。


         参考文章:[1] 在Eclipse 3.1中体验J2SE 5.0的新特性 : 第二部分 :注释类型 
                         [2] 了解何时使用 Override 和 New 关键字(C# 编程指南)  
                         [3] 使用 Override 和 New 关键字进行版本控制(C# 编程指南)

Feedback

#1楼    回复  引用  查看    

2006-07-03 16:24 by idior      
有点意思。
不过要不是动态代理的问题,显然是.net的方案更加安全。
用Annotation搞的话也算不错的补救。

我们为什么需要virtual关键字?

#2楼 [楼主]   回复  引用  查看    

2006-07-03 16:39 by FantasySoft      
@idior

对于为什么需要virtual关键字这个问题,莎士比亚的名句就已经告诉我们答案了:to be or not to be.

如果默认情况下,方法就是virtual的(如Java),那么就不需要virtual关键字了,但是需要引入final这个关键字告诉编译器哪些方法不允许被继承;如果默认情况下,方法不是virtual的(如C#),就需要通过virtual关键字告诉编译器哪些方法可以被继承了。

#3楼    回复  引用  查看    

2006-07-03 17:02 by Walkdan      
C#的override是从Object Pascal来的,也将Pascal的严谨带到了C#

#4楼    回复  引用  查看    

2006-07-03 17:46 by shenyi [未注册用户]
有点同意
也有点不同意

#5楼 [楼主]   回复  引用  查看    

2006-07-03 21:33 by FantasySoft      
@shenyi

您哪点同意,哪点不同意啊?恳请指教。 :)

#6楼    回复  引用  查看    

2006-07-04 02:15 by Lostinet      
new更体现了这种版本控制的思想。

#7楼    回复  引用  查看    

2006-07-04 08:54 by 武眉博<活靶子> [未注册用户]
可以想象如果你继承某类(xyzFrameWork)写了一个更适合自己项目的类
并且提供了一个方法签名为 void print();方法
运行很正常,过了两天xyzFrameWork出了一个新版本,修正了一大堆让你头痛
的bug 并且 提供了一个签名为 void print();的方法。但他的print 完全和你的print作了两样不同的事情。
怎么办,?
" new " 的作用就来了。
用你的 新签名方法 new void print(); 隐藏父类的方法。

#8楼    回复  引用  查看    

2006-07-17 16:09 by Anders06      
c#中声明virtual的方法效率低, 因为它需要动态去查找函数表,
普通的方法不会出现多态, 编译的时候就绑定了,效率高

具体可见 net本质论

#9楼    回复  引用  查看    

2006-08-22 12:46 by 阿保存 [未注册用户]
出现这样的错误,说明你对java还是不甚了解。

#10楼 [楼主]   回复  引用  查看    

2006-08-22 12:50 by FantasySoft      
@阿保存
恳请大侠指点。

#11楼    回复  引用  查看    

2006-09-03 17:41 by 一颗草      
你举的例子很不恰当,理由也不充足

你的DerivedClass本来就是想实现自己的public test(int)方法,不管覆盖也好,不覆盖也好,它对外的提供的服务始终没有改变.
对于ParentClass 它对外的服务根本就是改变了, 先不说已经拥有用户的类改变接口是一件设计编码中很忌讳的事,而且这重载方式保证了用户使用方式的最大程度稳定

试想一下,ParentClass有多个子类DClassA,DClassB,DClassC,
在DClassA有子类DClassA1,DClassA2等
它们的Test(int)方法,可能从祖先继承,可能自己重写,并且已经拥有多个自己的用户,你缺想把ParentClass的test(int)改为test(int,bool),什么语言会容忍你这种“严谨”!?

#12楼    回复  引用  查看    

2007-02-02 19:26 by bison [未注册用户]
第一种情况不是应该输出0吗?你的子类并没有改写父类

#13楼    回复  引用  查看    

2007-05-25 10:46 by a [未注册用户]
阁下貌似没有说出想说的

#14楼    回复  引用  查看    

2007-11-05 18:35 by 斧头帮少帮主      
经测试,21楼的兄弟说的对,输出结果为:0 而非你说的:1
因为你没有用override,所以a.test(0)永远会调用父类的test方法.

  class Program
    
{
        
static void Main(string[] args)
        
{
            ParentClass a 
= new DerivedClass();
            System.Console.WriteLine(a.test(
0));    //输出结果为0
            Console.ReadLine();

        }

    }


    
public class ParentClass
    
{
        
public int test(int i)
        
{
            
return i * 2;
        }

    }


    
public class DerivedClass : ParentClass
    
{
        
public int test(int i)
        
{
            
return i * 2 + 1;
        }

    }

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-07-03 16:05 编辑过
 
向地震灾区捐赠爱心