目录
6.1 类成员
6.2 成员修饰符的顺序
6.3 实例类成员
6.4 静态字段
6.5 从类的外部访问静态成员
6.5.1 静态字段实例
6.5.2 静态成员的生存期
6.6 静态函数成员
6.7 其他静态类。成员类型
6.8 成员常量
6.9 常量与静态量
6.10 属性
6.10.1 属性声明和访问器
6.10.2 属性示例
6.10.3 使用属性
6.10.4 属性和关联字段
6.10.5 执行其他计算
6.10.6 只读和只写属性
6.10.7 属性与公共字段
6.10.8 计算只读属性示例
6.10.9 自动实现属性
6.10.10 静态属性
6.11 实例构造函数
6.11.1 带参数的构造函数
6.11.2 默认构造函数
6.12 静态构造函数
静态构造函数实例
6.13 对象初始化语句
6.14 析构函数
6.15 readonly修饰符
6.16 this 关键字
6.17 索引器
6.17.1 什么是索引器
6.17.2 索引器和属性
6.17.3 声明索引器
6.17.4 索引器的set访问器
6.17.5 索引器的get访问器
6.17.6 关于索引器的补充
6.17.7 为Employee示例声明索引器
6.17.8 另一个索引器的实例
6.17.9 索引器重载
6.18 访问器的访问修饰符
6.19 分布类和分布类型
6.20 分部方法
之前的两章阐述了9中类成员类型的两种:字段和方法。在这一章中,我会介绍除事件和运算符之外的类型的类成员,并讨论其特征。我会在14章介绍事件。
表6-1列出了类的成员类型。已经介绍过的类型用菱形标记。将在本章阐述的类型用勾号标记,将在以后的章节阐述的类型用空的选择框标记。
在前面的内容中,你看到字段和方法的声明可以包括public和private这样的修饰符。这一章会讨论许多其他的修饰符。多个修饰符可以在一起使用,自然就产生了一个问题:它们需要按什么顺序排列呢?
类成员声明语句由下部分组成:核心声明、一组可选的修饰符和一组可选的特性(attribute)。用于描述这个结构的语法如下。方括号表示方括号内的成分是可选的。
【特性】【修饰符】 核心声明
至此,我直解释两个修饰符,及public和private 。在第24章中我会在介绍特性。
例如,public和static都是修饰符,可以用来一起修饰某个声明。因为它们都是修饰符,所以可以放置成任何顺序。下面两行代码语义等价:
类成员可以关联到类的一个实例,也可以关联到类的整体,既所有类的实例。默认情况下,成员被关联到一个实例。可以认为是类的每个实例拥有自己的各个类成员的副本,这些成员称为实例成员。
改变一个实例字段的值不会影响任何其他实例中成员的值。迄今为止,你所看到的字段和方法都是实例字段和实例方法。
例如,下面的代码声明了一个类D,它带有唯一整形字段Mem1.Main创建了该类的两个实例,每个实例都有自己的字段Mem1的副本,改变一个实例的字段副本的值不影响其他实例的副本的值。图6-2阐明了类D的两个实例、
除了实例字段,类还可以,拥有静态字段。
例如,图6-3左边的代码声明了类D,它含有静态字段Mem2和实例字段Mem1.Main定义了类D的两个实例。该图表明静态成员Mem2是与所有实例存储分开保存的。实例中灰色字段表明。从实例内部,访问或更新静态字段的语法和访问或更新其他成员字段一样。
在前一章中,我们看到使用点运算符可以从类的外部访问public实例成员。点运算符由实例名、点和成员组成。
就像实例成员,静态成员也可以使用点运算符从类的外部访问。但因为没有实例,所以必须使用类名,如下面代码所示:
下面的代码扩展了前文的类D,增加了两个方法:
静态成员的生命周期与实例成员的不同。
除了静态字段,还有静态函数成员。
例如,下面的类包含一个静态字段和一个静态方法。注意,静态方法的方法体访问静态字段。
可以声明为static的类成员类型在表6-2中做了勾选标记。其他类型成员不能声明为static。
成员常量类似前一章所述的本地常量,只是它们被声明在类声明中而不是方法内,如下面的实例:
然而,成员常量比本地常量更有趣,因为它们表现得像静态值。它们对类的每个实例都是“可见的”,而且即使没有类的实例也可以使用。与真正的静态量不同,常量没有自己存储位置,而是在编译时被编译器替换。这种方式类似于C和C++中的#define值。
例如,下面的代码声明了类X,带有常量字段PI。Main没有创建任何实例,但仍然可以使用字段PI并打印它的值。图6-6阐明了这段代码。
属性是代表类实例或类中的一个数据项的成员。使用属性看起来非常像写入或读取一个字段,语法是相同的。
例如,下面的代码展示了名称为MyClass的类的使用,它有一个公有字段和一个公有属性。从用法上无法区分它们。
与字段类似,属性有如下特性。
然而和字段不同,属性是一个函数成员。
属性是定的一组两个匹配的、称为访问器的方法。
图6-7展示了属性的表示法。左边的代码展示了声明一个名称为MyValue的int型属性的语法,右边的图像展示了属性在文本中将如何可视化地显示。请注意,访问器被显示为从后面伸出,因为它们不能直接被调用。这一点你很快就会看到。
set和get访问器有预定义的语法和语义。可以把set访问器想象成一个方法,带有单一的参数“设置”属性的值。get访问器没有参数并从属性返回一个值。
属性声明结构如图6-8所示。注意,图中访问器声明既没有显示的参数,也没有返回类型声明。不需要它们,因为它们已经在属性的类型中隐含了。
set访问器中的隐式参数value是一个普通的值参。和其他值参一样,可以用它发送数据到方法或这种情况的访问器块。在块的内部,可以像普通变量使用value,包括对它赋值。
访问器的其他终点如下。
下面的代码展示了一个名为C1的类的声明示例,它含有一个名称为MyValue的属性。
图6-9说明了这段代码。
就像之前看到的,写入和读取属性的方法与访问字段一样。访问器被隐式调用。
例如,下面的代码包含一个名称为MyValue的属性声明的轮廓。只使用属性名写入和读取属性,就好像它是一个字段名。
属性常和字段关联,我们在前两节已经看到了。一种常见的方式是在类中将字段声明为private以封装该字段,并声明一个public属性来控制从类的外部对该字段的访问。和属性关联的字段常被称为后备字段或后备存储。
属性和它们的后备字段有几种命定约定。一种约定是两个名称使用相同的内容,但字段使用Camel大小写,属性使用Pascal大小写,(在Canel大小写风格中,复合台词标识符中每个单词的首字母都是大写。)虽然这违反了“仅使用大小写区不同的标识符是个坏习惯”这条一般规则,但它有个好处,可以把两个标识符以一种有意义的方式联系在一起。
另一种约定是属性使用Pascal大小写,字段使用相同标识符的Camel大小写版本。并以下划线开始。下面的代码展示了两种约定:
属性访问器并不局限于仅仅对关联的后备字段进行传出数据。访问器get和set能执行任何计算,或不执行任何计算。唯一必需的行为是get访问器要返回一个属性类型的值。
例如,下面的示例展示了一个有效的(但可能没有用的)属性,仅在get访问器被调用时返回5.当set访问器被调用时,它不做任何事情。隐式参数value的值被忽略了。
要想不定义属性的某个访问器,可以忽略该访问器的声明。
按照推荐属性的编码实践,属性比公共字段更好,理由如下。
如果要发布一个由其他代码引用的程序集,那么第二点将会带来一点影响。例如,有时候开发人员会有公共的字段代替属性的冲动,因为如果以后需要字段的数据增加处理逻辑的话可以再把字段改为属性。这没错,但是如果那样修改的话,所有访问这个字段的其他程序集都需要重新编译,因为字段和属性在编译后的语义不一样。另外,如果实现的属性,那么只需要修改属性的实现,无需重新编译访问它的其他程序集。
迄今为止,在大多数示例中,属性都和一个后备字段相互关联,并且get和set访问器引用该字段。
然而,属性并非必须和字段关联。在下面的实例中,get访问器计算出返回值。
在下面的示例中,类RightTriangle自然而然的表示一个直角三角形。图6-11阐述了只读属性Hypotenuse。
因为属性经常被关联到后备字段,C#提供了自动实现属性,允许只声明属性而不声明后备字段。编译器会为你创建隐藏后备字段;并且自动连接到get和set访问器上。
自动实现属性的要点如下。
下面的代码展示了一个自动实现属性的示例。
属性也可以声明为static。静态属性的访问器何所有静态成员一样,具有以下特点。
例如,下面的代码展示了一个类,它带有名称为MyValue的自动实现的静态属性。在Main的开始三行,即使没有类的实例,也能访问属性。Main的最后一行调用一个实例方法。它从类的内部访问属性。
实例构造函数是一个特殊的方法,在它创建类的每个新实例时执行。
图6-12阐述了构造函数的语法。除了下面的这几点,构造函数看起来很像类声明中的其他方法。
例如,下面的类使用构造函数初始化字段。本例中,它有一个名称为TimeOfInstantiation的字段被初始化为当前日期和时间。
构造函数在下列方面和其他方法相似。
在使用创建对象表达式创建类的新实例时,要使用new运算符,后面跟着4类的某个构造函数。new运算符使用该构造函数创建类的实例。
例如,在下面的代码中,Class1有3个构造函数:一个不带参数,一个带int参数,一个带string参数。Main使用各个构造函数创建实例。
如果再类的声明中没有显示地提供实参构造函数,那么编译器会提供一个隐式的默认构造函数,它有以下特征。
如果你为类声明了任何构造参数,那么编译器将不会为该类定义默认构造函数。
例如,下面的代码中Class2声明了两个构造函数。
构造函数也可以声明为static。实例构造函数初始化类的每个新实例。而static构造函数初始化类级别的项。通常,静态构造函数初始化类静态字段。
下面是一个静态构造函数的实例。注意其形式和构造函数相同,只是增加了static关键字。
关于静态构造函数应该了解的其他重要内容如下。
下面的代码使用静态构造函数初始化一个名称为RandomKey的Random型私有静态字段。Random是有BCL提供的用于产生随机数类,位于System命名空间中。
再次之前的内容中你已经看到,对象创建表达式有关键字new后面跟着一个类构造函数及其参数列表组成。对象初始化语句扩展创建语法,在表达式的尾部放置了一组成员初始化语句。这允许你在创建新的对象实例时,设置字段和属性的值。
该语法有两种形式,如下面所示。一种形式包括构造函数的参数列表,另一种不包括。注意,第一种形式甚至不使用括起参数列表的圆括号。
例如,对于一个名称为Point类,它有两个公有整形字段X和Y,你可以使用下面的表达式创建一个新对象:
关于对象初始化语句要了解的重要内容如下。
下面的代码展示了一个使用对象初始化语句的实例。Main中,pt1只调用构造函数,构造函数设置了它的两个字段的值。然而,对于pt2,构造函数把字段的值设置为1和2,初始化语句把他们改为5和6.
析构函数(destructor)执行在类的实例被销毁之前需要的清理或释放非托管资源的行为。非托管资源是指通过Win32 API获得的文件句柄,非托管内存块,使用.NET资源是无法得到他们的,因此如果坚持使用.NET类,就不需要为类编写析构函数。因此,我们等到第25章再来描述析构函数。
字段可以用readonly修饰符 ,其作用类似于将字段声明为const,一旦值被设定就不能改变。
例如,下面的代码声明了一个名称为Shape的类,它有两个readonly字段。
this关键字在类中使用,是对当前实例的引用。它只能被用在下列类成员的代码块中。
很明显,因为静态成员不是实例的一部分,所以不能在任静态函数成员的代码中使用this关键字。更适当地说,this用于下列目的:
例如,下面的代码声明了类Myclass,他有一个int字段和一个方法,方法带有一个单独的int参数。方法比较参数和字段的值并返回其中较大的值。唯一的问题是字段和形参的名称相同,都是var1.在方法内使用this关键字引用字段,以区分这两个名称。注意,不推荐参数和类型的字段使用相同的名称。
假设我们要定义一个类Employee,它带有3个string型字段(如图6-13所示),那么可以使用字段的名称访问它们,如Main中的代码所示。
然而有的时候,如果能使用索引访问它们将会很方便,好像该实例是字段的数组一样。这正是索引器允许做的事。如果为类Employee写一个索引器,方法Main看起来就像是图6-14中的代码那样。谁注意没有使用点运算符,相反,索引器使用索引运算符,他由一对方括号和中间的索引组成。
索引器是一组get和set访问器,与属性类似。图6-15展示了一个类的索引器的表现形式,该类可以获取和设置string型的值。
索引器和属性在很多地方是相似的。
关于索引器,还有一些注意事项如下。
声明索引器的语法如下。请注意以下几点。
当索引器被用于赋值时,set访问器被调用,并接受两项数据,如下:
在set访问器中的代码必须检查索引参数,已确定数据应该存往何处,然后保存它。
set访问器的语法和含义如图6-17所示。图的左边展示了访问器声明的实际语法。右边展示了访问器的语义,把访问器的语法以普通方法的语法书法写出来。右边的图例表明set访问器有如下语义。
当使用索引器获取值时,可以通过一个或多个索引参数调用get访问器。索引参数指示获取那个值。
get访问器方法体内的代码必须检查索引参数,确定它表示的是那个字段,并返回该字段的值。
get访问器的语法和含义如图6-18所示。图的左边展示了访问器声明的实际语法。右边展示了访问器的语义,把访问器的语义以普通方法的语法写出来。get访问器有如下语义。
和属性一样,不能显示的调用get和set访问器。取而代之,当索引器用在表达式中取值时,将自动调用get访问器。当使用赋值语句对索引器赋值时,将自动调用set访问器。
在“调用“索引器时,要在方括号中间提供参数。
下面的代码为先前示例中的类Employee声明了一个索引器。
下面是一个附加的实例,为类Class1的两个int字段设置索引。
只要索引器的参数列表不同,类就可以有任意多个索引器。索引器类型不同是不够的。这叫做索引器重载,因为所用的索引器都有相同的“名称”:this访问引用。
例如,下面的代码有3个索引器:两个string类型的和一个int类型的。两个string类型的索引中,一个带单独的int参数,另一个带两个int参数。
在这一 章中,你已经看到有两种函数成员带get和set访问器:属性和索引器。默认情况下,成员的两个访问器有和成员自身相同的访问级别。也就是说,如果一个属性有public访问级别,那么它的两个访问器都有同样的访问级别,对索引也一样。
不过,你可以认为两个访问器分配不同的访问级别。列如,如下列代码演示了一个非常常见而且重要的例子,那就是为set访问器声明为private,为get访问器声明为public。get之所以是public的是因为属性的访问级别就是public的。
注意,在这段代码中,尽管可以从类的外部读取该属性,但却只能在内部类的内部设置它(本例中是在构造函数内设置)。这是一个非常重要的封装工具。
访问器的访问修饰符有几个限制。最重要的限制如下。
图6-19阐明了访问级别的层次。访问器的访问级别在图标中的位置必须比成员的访问级别位置低。
例如,如果一个访问属性的访问级别是public,在图里较低的4个级别中,可以把任意一个级别给他一个访问器。但如果属性的访问级别是protected,唯一能对访问器使用的访问修饰符private。
类的声明可以分割成几个分部类的声明。
每个局部变量声明必须被标注为partial class ,而不是单独的关键字class。分布类声明看起来和普通类声明相同,除了那个附加的类型修饰符partial。
例如,图6-20中左边的边框表示一个类声明文件。图右边的框表示相同的类型声明被分割成两个文件。组成类的所有分部类声明必须在一起编译,使用分部类声明的类必须有相同的含义,就好像所有类成员都声明在一个单独的类声明体内。
分部方法是声明在分部类中·不同部分的方法,分部方法的不同部分可以声明在不同的分部类中,也可以声明在同一个类中,分部方法的两个部分如下。