您的位置:首页 > 编程语言 > C#

对于C#基本知识的一点感悟(1)——关于C#语法的抽象

2012-09-03 22:23 316 查看
.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替换为某种特定的类型,AutoMap<Person>
AutoMap<Object>等。但注意,当你写下AutoMap<Person>这个样子时,实际上是等于创建了一个AutoMap<Person>类型,同理写下 AutoMap<Object>时,则创建了一个 AutoMap<Object>类型。它的确足够特别:你定义的是一个“类型模版”,亦即模板的模板(类型本身就是一个模板)。所以,只有当你把T指示明确时,这个类型(模板)才存在,然后用它来发挥类型所应有的功能。同样当你把T指定为另一明确时,又定义了另一个类型。

事实上,有这么些定义类型的关键字,不同的关键字既定义了类型,同时对相关类型提出了限制,这个限制是因为编译器背后要把我们的关键字转换为很复杂的操作,就比如struct,它指出了必须把这个类型直接继承自valuetype。但是,为了避免开发者破坏继承关系,struct定义的类型除了能显式继承自接口外,不能继承子任何其他值类型或者引用类型。此外,不同的关键字定义的类型对 该类型能拥有的成员(组成部分)也提出了很多限制。比如C#就不允许在interface类型里定义字段或者属性等一些成员。

这就引到了关于类型的第二个部分:类型的成员。利用关键字,我们只是告诉了编译器我们想要定义一个类型,可是要完成一个类型的定义,我们必须还要指定类型的成员有哪些。否则,定义空类型意义不大。 对于类型的成员,不同种类的类型有些限制外,但所有的类型能包括的成员均出自于一个类型成员集合,他们就是字段、属性(本质上也是方法)、方法、事件(委托)。他们分工也很明确:字段专门负责存储相关数据、方法则负责进行操作,属性不过是更专一的处理简单逻辑的和字段联系更精密的操作,事件则提供了一种注册、通知的机制。各司其职,各有个的定义方式,各有个的限制。但无论是定义哪个类型,无论是定义哪种类型,只要在该类型支持的前提下,定义这些成员的形式都是一样的。而且,

相比于这些成员的定义,更重要的是对于这些成员必须做两种区分,关于这些成员的所有权的区分。一开始就声明了,类型同样是对象,而在类型里定义的成员,有些我们是希望它是为类型的实例所拥有的,这类为实力成员。但同样有些成员我们是希望仅仅为类型所拥有。有一点要知道:对于实例对象,一个类型可以同时存在多个实例,但是对于类型对象,运行时会确保一个程序里只会有一个类型对象。所以,类型成员(尤其是属性和字段),则意味着这个字段或者属性在整个程序里只存在一个,当在程序的某个地方修改了,程序的其他地方对它的读取均会得到被修改后的值。所以类型成员,提供了不同的对象来“共享”某些数据的一种解决方案。这里的共享仅仅是从效果上来看是共享了而已。同样的道理,这个实例成员和类型成员的区别,适用于所有的类型(除非某种类型有特别的限制)。

其实,对于源代码而言,我们能做的更多的工作是设计定义合适的类型,这是我们能自己控制最多的部分。而C#则是以一种面向对象的统一而又严格的方式去为我们提供定义各种类型的机制,最终要的是这种以Object为出发点,以类型和实例为基本区分,以关键字去减轻省开发人员的工作而通过编译器弥补额外的工作,最终严格以面向对象的方式去组织代码。对于我们自定义的类型如此,对于C#提供的类库里的类型如此,对于.net的绝大多数扩展的功能库同样如此。

当类型定义好后,当用类型来生成一个实例对象时,这更多地工作则是运行时去完成的过程了,除了一少部分我们可以干预外,我们能做的很少。而从定义好类型,甚至写好通过类型创建对象,利用对象来处理各种逻辑任务的源代码后,放才是完成了源代码编写的过程。写好源代码,对.net而言,只是冰山一角。前者能便于我们去组织代码,向编译器表达我们的意愿,而后者的运行机理,则能让我们对它的过程认识的更全面,从而有可能写出运行效率更高的代码,或者解决一些意想不到的问题。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: