C#---类型和字段

     现在总算是进展到OOP最重要的类型了,感觉很兴奋但又困惑,因为C#中有很多与其他OOP语言像是java大不相同的处理。

1.可见性

      C#中的可见性很多与java重叠并且意义相同,但有一个特别的internal(internal其实并不特别)。internal表示仅对定义程序集(assembly)中的所有代码可见,至于其他程序集不可见。我的第一眼感觉就是java的默认访问权限,即包访问权限,至少它们是类似的。事实上,CLR中的程序集的确对应包,因为它的定义就是一个或多个模块/资源文件的逻辑性分组,而且是重用,安全性及版本控制的最小单元,这些都符合包的特点。正如包访问权限是java的默认权限一样,C#的默认权限就是internal。

      在C#中,当我们在派生类中覆写基类的抽象方法或虚方法时,我们不能修改它们的可见性,就算是放宽可见性也不行(在java中,这是允许的)。

2.静态类

      静态类的唯一作用就是将一组相关成员组合到一起,其实也就是静态成员,因为静态类无法访问实例成员,它并没有this引用。我们可以将一个类声明为静态类,但是结构不行,虽然结构和类非常相似。静态类直接继承自System.Object,因为继承自其他类是枉然的,它根本就无法创建对象实例。静态类还不能实现任何接口,同样也是因为只有使用对象实例时才能发挥接口的作用。静态类中的所有成员都是静态的,原因前面已经讲过了。静态类也不能作为字段,方法参数或局部变量使用,因为它们默认都代表引用一个实例变量。静态类在编译器中标记为abstract(抽象)和sealed(密封),这是值得注意的。

     很少使用静态类,像是java也比较少,因为定义一个类,更多是为了封装数据和行为,并提供访问接口,但静态类根本就是一个封闭的集合,就像前面讲的,它只是为了封装静态成员而已。

3.分部类

     关键字partial非常神奇,第一次接触这个关键字以及了解它的用途之后,我对C#的包容性真的很震惊,它似乎就是一个集合体,把很多东西都搅合在一起。

     partial关键字告诉编译器,这个类,结构或者接口的定义源码可能要分散到一个或多个源代码中文件中。也许我们会很奇怪:为什么要将我们的源码分散到多个文件中呢?原因有以下几个方面:

    1.源代码控制:程序员可以将一个类型的源码从一个源码控制系统中签出(check out)进行修改,如果将类型的源码分散到多个源码文件中,就能使多名程序员同时修改单互不影响。

   2.在同一个文件中,将一个类型或结构分解成不同的逻辑单元:这适合测试类型的功能,我们可以在一个源码文件中反复声明同一个分部类型,然后在这个类型的多个部分实现不同功能,然后测试它的运行情况。我们可以选择注释掉某个部分,或者是用另一个部分来代替它。

  3.代码拆分:如果熟悉ASP.Net的同学,对拆分一定很熟悉,是的,就是这个拆分。我们在ASP.Net中,经常会将某个组件放进我们的源码中,然后我们就会发现,该组件的代码竟然插入到我们的源码中,如果只是插入,那还没什么,但是我们点击拆分窗口,就可以查看我们的源码的运行情况。这就是利用了partial关键字所起到的神奇作用,不信,大家在使用ASP.Net的时候,可以看看整个源码。

    partial在组件软件编程(Component Software Programming,CSP)发挥了很大的作用,同时它也也是OOP思想的一个极佳体现。

4.其他关键字

     其他关键字像是virtual,new我们都很熟悉(对于virtual由于之前学C++的时候不扎实,所以在写C#例程的时候犯了错误,以为它是java的abstract,没有写方法体),virtual表明该方法可以被派生类覆写(override方法也可以,因为它本身就是一个virtual或abstract方法的覆写版本),而且C#要求覆写方法时必须注明override(java并不需要,但我们需要添加一个标签@Override提醒这是基类的覆写版本),而new在C#中却有新的用法:

public new void Show(){}

public new String name = "";

      它们表明,这是派生类自己的字段和方法,而不是基类的。这种情况适合我们在派生类中定义与基类同名的方法或字段,如果没有这个new声明,默认采用基类的方法和字段,但编译器会提出警告,因为同名的方法或字段肯定是为了重新定义(不同于覆写,C#不支持非虚方法和字段的覆写),我们完全可以直接使用基类的方法和字段。
     前面提到的sealed,表示该类型不能被派生,它就像java的final。它并不仅仅用于类型上,连方法也可以,表示我们不想继承自该类的类型再覆写该方法 。

5.类型的设计问题

      类型有这么多的关键字和可见性,导致我们在设计类的时候可能就会有疑问:到底我该怎样限定它们的可见性和添加合适的关键字呢?这个问题其实我在Java那里就已经有较为基本的认识,这里结合C#重新说一下。

      C#编译器默认生成非密封类,但类应该是密封的。原因有下面几个方面:

      1.版本控制:如果类一开始是密封的,在可预见的未来我们就可以在不破坏兼容性的前提下更改为非密封的,但如果类是非密封的,就没有机会将它改为密封的(如果这样做,后果很严重,因为它后面的一系列派生类就会全部阵亡)。java中我还真没有想过这个问题,因为继承是扩展旧类的方法,虽然我们更喜欢用接口。

      2.性能:调用虚方法的性能比不上调用非虚方法,因为CLR必须在运行时判断对象的类型,而如果是密封类的虚方法,编译器就会当做非虚方法进行调用,因为不可能会有派生类覆写该方法。
      3.安全性和可预测性:将一个方法,属性或事件设为virtual,是一件很冒险的事,因为基类会丧失对它的行为和状态的部分控制权,因为派生类可以选择覆写它们。

      但密封类缺点也是非常明显的,就是造成类型的用户巨大的不方便,因为类功能不会一直不变,类型的使用者会想要为该类型添加新的功能。

      所以,这里就有一个折衷的方法:在实现自己的类时,确保将继承的所有虚方法(包括System.Object中定义的)都密封,同时,不要定义将来可能对版本控制造成负担的任何方法,比如受保护方法或虚方法。这种方法适合所有OOP语言,包括java。

      自定义类型的时候,我们还可以遵循一些原则:

      1.除非确定要将该类作为基类使用,并允许派生类对它进行特化(specialization,就是我们添加新的行为或属性),否则就显式的指定为sealed,而且采用默认的internal,除非我希望公开该类。就算我认为该类有必要被别人继承,但不想运行对它进行特化,就可以采取上面那个方法。

      2.数据字段,方法,属性和事件毫无疑问都设为private,体现良好的数据封装性。幸好C#编译器默认就是这样。

      OOP的世界中,设计类型是我们最大的工作,如何设计一个复用性高,封装性好,而且耦合度低的类,是一个艰难的工作,甚至可以说是一门艺术,就像OOP大师那样。

6.字段

      最后我们简单的讲一下字段(field)。

     C#的字段并没有什么特别之处,但有关它的修饰符是值得说一下的,那就是readonly。readonly字段只能在构造器中写入,也就是对象一旦创建,我们就无法写入了(可利用反射修改,因为反射可以获取构造器)。注意,如果是引用类型的字段标记为readonly,不可改变的是引用而不是引用所指的对象,就像java的final引用。

    

 

    

 

posted @ 2013-03-14 23:27  文酱  阅读(3425)  评论(0编辑  收藏  举报