日期:2014-05-17 浏览次数:20917 次
.net开发接近有一年的光阴,前段时间学了一点C++基本语法,最近又开始重新学C。作为.net的基础,当再次回味C#时,忽然发现C#基础知识亦不是想象中的那么简单。而且作为一标准的面向对象语言,建立于.net之上的种种拓展性功能,皆可从这些最基础的部分映射出来。这一基础知识大概分以下三个层次:
从源程序角度看:是C#基本语法的组织框架,亦即C#语言的语法抽象——面向对象的特性。几乎C#所有的语法均以这一语法抽象为约束而扩展出来。
从中间代码的角度看:是我们的源程序被编译器作了怎样的扩展,这个扩展当然不是指编译器做的各种优化,而是为了我们编写代码的便利,C#提供了许多简化的语法,但是编译器最终还是要确保我们简略的源代码最终符合它所设定的那个基本约束,以及它需要为中间代码能被JIT执行做好铺垫——很明显计算机/cpu根本不会识别我们的对象。
从程序执行的角度看:则是从程序逻辑的执行、以及作为一种托管的语言,CLR如何在执行我们写的源代码的同时去发挥托管的优势(或者是CLR是如何去托管代码的)。
如果再深一点,则是CLR本身的执行机制了,这个东西貌似比较吓人了。
当前我们重点关注第一个层次:C#语法的抽象。
C#是一种逻辑严密的面向对象语言:它不允许有任何东西脱离于对象之外——所以,比如有类型Person:Person p1=new Person()。 不单单各种类型的实例,在此处是p1,是对象(实例对象),类型本身在C#看来也是一个对象(类型对象),比如Person 这个类,他同样是对象。(暂且不用管对象在内存里是怎样的存在)。既然是对象,它至少是某一种类型的实例,比如p1,它就是Person类型的实例,(更通俗的讲,p1是Person类型的)。同样,Person本身也是某种类型的实例(其实它是[System.]Type类型的实例),那么,System.Type这个类型又是谁的实例呢?这个比较特殊,Type类型作为一种对象,它的类型就是它本身。也就是说:Type类型是Type类型的实例。一切终究还是划上了圆。
如此,我们可以分离出C#里最本质的两个东西:类型和类型的实例(通常所讲的某一个对象)。先来说说类型吧:虽然类型和对象的先后有点鸡和蛋的关系,但是从语法上来看,我们是先定义出了某一种类型,才会有这种类型的实例。
就类型而言,C#同样有面向对象方面的严密性:既然我们有“所有的’类型‘”这一概念,亦即就各种各样的类型而言,它们终究是可以共享类型这一统一的概念,(亦即它们都是类型,从面向对象的角度看,有一种A is B的道理),所以,"所有的 类型 均可最终继承自同样的某个类型”成立,这个类型就是C#语法组织形式上和逻辑上起点Object。此处还要提一下它的定义方式:public class Objcet{....}。 作为对C#里root级类型的定义,class关键字在这里有着最广泛的意义:class就是定义了一个类型,这个类型是此外所有其他类型的基类。而除此以外其他地方再使用class时,你仅仅是定义了一个直接继承自Object的类型,不使用class,你同样可以定义一个类型出来,大部分情况下这种方式定义的类型不会直接继承自Object,但是interface这个关键字同样可定义出直接继承自Object的类型。也就是说,除了定义Object类型以外,其他地方的class关键字均不如此处的级别"高"。
接下来,我们就来看看类型的定义。定义一个类型,亦即创造出谋一种类型,有了这个定义好的类型,你可以直接或间接生成无限多个由这同一个类型作为模子而刻出来的实例,亦即一般而言上的无限多个对象。也就是说,当我们想让我们的代码能够更节省,而且想把相似的代码只写一次时,我们就很 想用一个定义某一种符合我们需求的对象出来,这点共识很重要,因为后面C#考虑引入一种类型来处理某个问题或是直接利用已经存在的对象来解决某个问题时,很大一方面是出于了这方面的考虑。
定义一个类型最基本的要素:可见性和版本控制性就不多讲了,分别用于处理你想让类型在哪个范围可见和是否能被继承。同时你还会考虑是否要禁止直接利用自己的类型来创建对象。
除此之外,更为重要的一点则是 我们如何告知编译器我们是想要定义一个类型,而不是一个方法,也不是声明一个类型的实例。这就需要我们使用某一种关键字(比如class)来告诉编译器了。这些关键字只是用来作为开发者和编译器之间的一种约定,实际上在面向对象里面,它是没有任何意义的:比如说class,它是什么呢?是个类型?明显不是,是种操作符?也不是。既然是种约定,完全可以为不同的关键字 指定不同的意义:亦即只要是这些关键之是用来定义一个类型就可以了。
说到这里,必须提一点每一个学托管语言都会严格区别的类型类别:值类型和引用类型。毕竟在托管语言里,它们的实现机制有明显的区别。所以,C#在定义类型语法上,必须能够让你选择是定义一种值类型,还是引用类型。这种机制即要我们使用起来很便捷,而且还必须满足面向对象的那一套规则。所有的类型(无论值类型还是引用类型)都最终继承自Object。
C#本身定义了这样一种类型:public abstract class System.ValueType。用的是class关键字,所以它是直接继承自Object类型。而且规定,所有的值类型必须继承自ValueType。而在C#里,只有结构和枚举两种值类型,当使用struct关键字时,其实就是定义了一个直接继承自ValueType的类型,而使用enum关键字时,则是定义了一个类型,它直接继承自Enum(c#提供的)类型,而Enum则继承自ValueType。之所以还专门作了Enum类型的值类型和普通一般的值类型,是因为C#想为枚举提供额外的支持。这是它一贯的伎俩。于是,对我们开发者而言,当需要定义值类型的类型时,仅仅只要使用struct或者enum关键字即可,至于这后面的各种值类型的特性、以及种种继承关系,实际是编译器所做的工作。
要定义引用类型的类型就很简单了:struct和enum关键之以外的,所有其他用来定义类型的关键字均会定义引用类型的类型。class 就不多说了,interface则比较特殊些,但要注意的是:它同样是定义了一个直接继承自Object的类型(除非你显式的让它继承自其他l类型),逻辑上必须是一致的。
此外还有其他比较迷惑的像delegate,表面上是我们定义了一个委托,但想一想委托的通用性,而且我们对委托的使用方式(用我们定义的委托类型去实例化一个对象后,才可使用),同样delegate这个类型和其他类型有很多区别,但是只要注意到它仍然是一个类型时,一切就不是太奇怪了。
另外还有最最特殊的一中“类型”:泛型类型。这么说是有问题的,因为泛型类型它本身并不是一个类型,虽然我们使用了class关键字,反证如下:如果它是类型,必然最终继承自Object类型,而且它必然也是个对象。但比如定义了如下:public class AutoMap<T>{...} ,当你写下:AutoMap<T>时,视乎是想如Person一样,能够调用它继承自Object的类型方法,但实际是AutoMap<T>这样写没有任何意义。相反,你必须把T替换为某种特定的类型,Aut