Net中的关键字有很多,我们最常见的就有new、base、this、using、class、struct、abstract、interface、is、as等等。有很多的,在这里就介绍大家常见的,并且有一定代表性的关键字。它们之间有很多相似或者相对的,也是容易混淆的,或者有些特殊用法我们不知道的。
1、new关键字
new关键字是我们学习面向对象最常用的,new一个实例对象,这个是大家最不陌生的一个,估计天天都在写这个。那么下面我们看一下几个问题,带着问题一起去看看。
new 一个class对象和new 一个struct或者enum有什么不同?
new在.net中有几个用途,除了创建对象实例,还能做什么?
new运算符,可以重载吗?
泛型中new有什么作用?
new一个继承下来的方法和override一个继承方法有和区别?
int i和int i=new int()有什么不同?
带着这些问题,我们一起来看一下new关键字。在MSDN中解释,new的用法为以下几种:
(1)作为运算符,用于创建对象和调用构造函数;
new运算符用于返回一个引用,指向系统分配的托管堆的内存地址。
new一个class时,new完成了两个方面的操作,一是用newobj命令来为实例在托管堆中分配内存,二是调用构造函数来实现对象的初始化。
new一个struct时,new运算符用于调用其带构造函数,完成实例的初始化。
new一个int时,new运算符用于初始化其值为0。
注意:值类型和引用类型在分配内存时是不同的,值类型分配在线程的堆栈上,不受GC控制;引用类型分配在托管堆上,内存收集由GC完成。
new运算符不可以重载(overload);new分配内存失败,会引发outofmemoryexception异常。
(2)作为修饰符,用于向基类成员因此继承成员;
作为修饰符,基本的规则可以总结为:实现派生类中隐藏方法,则基类方法必须定义为virtual,new作为修饰符,实现隐藏基类成员时,不可和override共存。原因是二者相斥:new用于实现创建一个新成员,同时隐藏基类的同名成员;而override用于实现对基类成员的扩展。
(3)作为约束,用于在泛型声明中约束可能用作类型参数的参数的类型;
new约束指定泛型类型声明中的任何类型参数都必须有公共的无参数的构造函数。当泛型类创建类型的新实例时,将此约束应用于类型参考。
注意:new作为约束和其他约束共存,必须在最后指定。
2、base和this关键字
在这里把base和this放在一起来说,想必大家都经历过,这两个关键字容易混淆。两个关键字的确有很多相似的地方,但是还有不同的地方。下面看几个问题,带着问题去思考一下。
是否可以在静态方法中使用base和this?
base常用于哪些方面?this常用于哪些方法?
可以base访问基类的一切成员吗?
如果多层继承,派生类中base指向哪一层呢?base访问,则应该是直接父类实例,还是最高层实例呢?
base和this应用于构造函数时,继承类对象实例化的执行顺序?
base和this,顾名思义就是用于实现继承机制的访问操作,来满足对象成员的访问,从而为多态机制提供更加灵活处理方式。
(1)base用于在派生类中实现对基类公有或者受保护成员的访问,私有成员不可以访问的。但是只是局限在构造函数、实例方法和实例属性服务器中。
调用基类已被其他方法重写的方法。指定创建派生类实例时应调用的基类的构造函数。在派生类对象初始化时和基类进行通信。
base在多层继承中,可以指向父类的方法有两种:一是有重载存在情况,base将指向直接继承的父类成员的方法;二是没有重载存在的情况下,base可以指向任何上级父类的公有或者受保护的方法。
(2)this用于引用类的当前实例,也包括继承而来的方法,通常可以隐藏this的。
限定被相似的名称隐藏的成员。将对象作为参数传递到其他方法。声明索引器。
this指对象本身,用于访问本类的所有常量、字段、属性和方法成员,而且不受级别限制。this只是适用在对象内部,因此外部看不到,还有在静态方法中不能使用this。
注意:对重写父类方法,最终指向了最高级父类的方法成员。
尽量少用或者不用base和this。在静态成员中不允许使用base和this,base是为了实现多态而设计的。base和this只能有一个指定构造函数,也就是二者不能同时作用在一个构造函数上。base用于访问派生类,而this只能使用于本类中。除了base,访问基类成员的另外一种方式:显示的类型转换来实现,只是该方法不能为静态方法。
3、using关键字
在net中最常见的代码莫过于在程序文件开头引入system命名空间,using system。类似Java语言中import指令,而不同于C语言中#includ 指令,用于引入实际的类库。
命名空间是net程序在逻辑上的组织结构,而并非实际的物理结构,是一种避免类名冲突的方法,用于将不同的数据类型组合划分的方式。
using引入命名空间,并不等于编译器在编译时加载命名空间所在的程序集,程序集的加载决定于对该程序集是否存在调用操作,如果不存在任何调用,则不会加载using引入命名空间所在程序集。因此,在源文件头,引入多个命名空间,并非加载多个程序集,不会造成“过度引用”。
(1)using作为引入命名空间指令;
(2)using为命名空间创建别名,同样也为创建类型别名。而创建别名的另一个重要的原因在于同一个CS文件中引入的不同命名空间中包括了相同名称的类型,为了避免出现名称冲突可以设定别名来解决。
eg: using MyConsole=System.Console;
(3)using可以强制清理资源,允许指定何时释放对象的资源,实际上在using语句结束时会自动调用对象的dispose()方法。对象实现了IDispose接口,才能使用using进行强制清理资源。dispose方法用于清理对象封装的非托管资源,而不是释放对象内存,对象的内存依然由GC控制。
using语句适用于清理单个非托管资源的情况,而多个非托管对象清理的时候最好以try catch来实现,因为嵌套的using语句可能存在隐藏的bug,内存using块引发异常时,将不能释放外层using块的对象资源。
using语句支持初始化多个变量,但前提是这些变量的类型必须相同。
using中初始化对象,可以在using语句之前声明。
4、class和struct关键字
对于class和struct,我们通常会混淆的,首先我们感觉是语法相同,但是的确不一样的,class是引用类型,而struct是值类型,它们在内存中的分配情况有所区别。
(1)class(类)是一种自定义数据结构类型,通常包含字段、属性、方法、构造函数、索引器、操作符等。
所有类都继承system.object类,new一个类的实例时,对象保存了该实例实际数据的引用地址,而对象的值保存在托管堆中。
(2)struct(结构)是一种值类型,用于将一组相关的信息变量组织为一个单一的变量实体。
所有的结构都是继承system.valuetype类,struct实例分配在线程的堆栈上,它本身存储了值,而不包含指向该值的指针。我们在使用的时候,可以将其当作int和char这样的基本类型来对待。
二者的相同点和不同点:
- class是引用类型,继承system.object类;struct是值类型,继承system.valuetype类,因此不具备多态性,但是system.valuetype是个引用类型。
- class表现为行为;而struct常用于存储数据。
- class支持继承,可以继承类和接口;而struct没有继承性,不能从class继续,也不能作为class基类,但是struct支持接口继承。
- class可以声明无参构造函数,可以声明析构函数;而struct只能声明带参数构造函数,且不能声明析构函数,因此struct没有自定义的默认的无参构造函数,只能默认将所有值定义为0值。
- 实例化时,class要使用new关键字;而struct可以不适应new关键字,不以new来实例化,所有字段都是未分配的状态。
- class可以是抽象类(abstract),可以声明抽象函数;而struct为抽象,不能声明抽象函数
- class可以声明protected成员、virtual成员、sealed成员和override成员;而struct不可以,但是struct可以重载object的3个虚方法,equals()、tostring()、gethashtable()。
- class的对象复制分为浅拷贝和深拷贝,必须经过特别的方法来实现复制;而struct创建对象复制简单,可以直接用等号连接。
- class实例由GC来进行回收;而struct变量使用完之后立即自动解除内存分配。
- 作为参数传递时,class变量是按地址方式传递;而struct变量是以值方式传递的
总结:class是一个可以动的机器,有行为、有多态、有继承;而struct就是一个零部件箱,组合了不同结构的零件。本质区别:class是引用类型,内存分配在托管堆上;而struct是值类型,内存分配于线程的堆栈上。
5、abstract和interface关键字
抽象类和接口,也是大家比较容易混淆的一组。接口也是一种特殊的抽象类,二者有相似和不同。
(1)abstract(抽象类)提供了多个派生类共享基类的公共定义,它既可以提供抽象方法,也可以提供非抽象方法。抽象类不能实例化,必须通过派生类继承实现了抽象方法,因此对抽象类不能使用new关键字,也不能被密封。如果派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。实现抽象方法由override
(2)interface(接口)包含一组虚方法的抽象类型,其中每一种方法都有其名称、参数和返回值。接口方法不能包含任何实现,CLR允许接口可以包含事件、属性、索引器、静态方法、静态字段、静态构造函数及常数。一个类可以实现多个接口,当一个类继承某个接口时,它不仅要实现该接口定义的 所有方法,还要实现该接口从其他接口中继承的所有方法。
二者的相同和不同点:
- 都不能被直接实例化,都可以通过继承实现其抽象方法
- 都是面向对象面向对象编程的技术基础,实现了诸多的设计模式
- 接口支持多继承;抽象类不能实现多继承
- 接口只能定义抽象规则;抽象类既可以定义规则,还可能提供已实现的成员
- 接口时一组行为规范;抽象类是一个不完全的类,着重族的概念
- 接口可以用于支持回调;抽象类不能实现回调,因此继承不支持
- 接口只包含方法、属性、索引器、事件的签名,但不能定义字段和包含实现的方法;抽象类可以定义字段、属性、包含有实现的方法
- 接口可以作用于值类型和引用类型;抽象类只能作用于引用类型。struct就可以继承接口,而不能继承类
使用规则和场合
- 面向对象思想的一个重要的原则:面向接口编程
- 抽象类应主要用于关系密切的对象;而接口最适合为不相关的类提供通用功能
- 接口着重于can-do关系类型;而抽象类则偏重于is-a式的关系
- 接口多定义对象的行为;抽象类多定义对象的属性
- 接口定义可以使用public、protected、internal和private修饰符,但是几乎所有接口都是为public
- 接口不能改变,扩展时候需要增加新的接口,而不能改现有的接口
- 尽量将接口设计成功能单一的功能块,以I开头为接口名称
- 从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实现
- 对抽象类不能使用new关键字,也不能被密封
- 在抽象方法声明中不能使用static或者virtual修饰符
- 当差异较大的对象间寻求功能上的共性时,使用接口;共性多的对象间寻求功能上差异,使用抽象类
- 如果设计小而简单的功能块,使用接口;设计大而复杂的功能单元,使用抽象类
- 如果要在组件的所有实现间提供通用的已实现功能,则使用抽象类,抽象类允许部分实现;而接口不包含任何成员的实现
6、is和as关键字
先了解一下类型转换,显示转换和隐式转换。
- 任何类型都可以安全转换为基类类型,可以由隐式转换来完成
- 任何类型转换为其派生类型时,必须进行显示转换 (类型名)对象名
- 使用gettype可以取得任何对象精确的类型
- 基本类型可以使用convert类实现类型转换
- 除了string意外的其他类型都有parse方法,用于将字符串类型转换为对应的基本类型
- 值类型和引用类型的转换机制成为装箱和拆箱
is和as操作符,实现类型转换
(1)is使用规则:
检查对象类型的兼容性,并返回结果,true或者false
不会抛出异常
如果对象为null,则返回值永远为false
eg:object o=new object(); if(o is A)
(2)as使用规则:
检查对象类型的兼容性,并返回结果,如果不兼容就返回null
不会抛出异常
如果判断为空,则强制执行类型转换将抛出nullreferenceexception异常
eg:object o=new object(); B b=o as B;
二者对比,as操作符在执行效率上比is略胜一筹