6.1 类成员
成员修饰符的顺序
类成员声明语句由下列部分组成:核心声明、一组可选的修饰符(modifier)和一组可选的特性(attribute)。用于描述这个结构的语法如下。方法号表示方括号内的成分是可选的。
[特性] [修饰符] 核心声明
- 如果有修饰符,它必须放在核心声明之前。
- 如果有特性,它必须放在修饰符和核心声明之前。
如果一个声明有多个修饰符,它们可以以任何顺序放在核心声明之前。如果有多个我,它们可以以任何顺序放在修饰符之前。
下面两行代码是语义造价的:
public static int MaxVal;
static public int MaxVar;
特性 | 修饰符 | 核心声明 | |
---|---|---|---|
字段声明 |
public
private
static
const
| Type FieldName; | |
方法声明 |
public
private
static
|
ReturnType MethodName(ParameterList)
{.....}
|
6.2 实例类成员
类成员可以关联到一个实例,也可以关联到类的整体。默认情况下,成员被关联到一个实例。可以想象类的每个实例拥有自己的各个类成员的拷贝,这些成员称为实例成员。
改变一个实例字段的值不会影响任何其他实例中成员的值。迄今为止,所看到的字段和方法都实例字段和实例方法。
6.3 静态字段
除了实例字段,类还可以拥有静态字段。
- 静态字段被类的所有实例共享,所以实例都访问同一内存位置。因此,如果该内在的值被一个实例改变了,这种对所有的实例都可见。
- 使用static修饰符声明一个字段为静态。
从类的外部访问静态成员
点运算符被用于从类的外部访问实例的成员。点运算符由实例名、点、成员名组成。
就像实例成员,静态成员也可以使用点运算符从类的外部访问。但因为没有实例,所以必须使用类名。
静态成员的生存期
实例成员在实例被创建时开始存在,当实例被销毁时停止存在。然而,静态成员即使没有类的实例也存在并可以访问。
6.4 静态函数成员
除了静态字段,还有静态函数成员
- 如同静态字段,静态函数成员独立于任何类实例。即使没有类的实例,仍然可以调用静态方法。
- 静态函数成员不能访问实例成员。,它们能访问其他静态成员。
6.5 其他静态类成员类型
可以声明为static的类成员类型有:
- 数据成员(存储数据):字段。
- 函数成员(执行代码):方法、属性、构造函数、运算符、事件
成员常比本地常量更有趣,它们表现得像静态变量。它们对类的每个实例都是“可见的”,而且即使没有类的实例它们也可以使用。
然而,与真正的静态量不同,常量没有自己的存储位置,而是在编译时被编译器替换。这种方法类似于C和C++中的#define值。因此,虽然常量成员表现像一个静态变量,但不能声明一个常量为static。
6.7 属性
属性是代表类的实例或类中的一个数据项的成员。
使用属性看起来非常像写入或读取一个字段,语法是相同的。从使用上无法区分它们。
就像字段,属性有如下特征:
- 它是命名的类成员。
- 它有类型。
- 它可以被赋值和读取。
- 它不为数据存储分配内存。
- 它执行代码。
- set访问器用于为属性赋值。
- get访问器用于从属性获取值。
- set访问器总是:
- 一个单独的、隐式的值参,名称为value,与属性的类型相同。
- 一个返回类型void。
- get访问器总是:
- 没有参数。
- 一个与属性类型相同的返回类型。
- set访问器总是:
set访问器中的隐式参数value是一个普通的值参。和其他值参一样,可以用它发送数据到方法体或这种情况中的访问器块。在块的内部,可以像普通变量那样使用value,包括对它赋值。
访问器的其他重点有:
- get访问器的所有执行路径必须包含一条return语句,返回一个属性类型的值。
- 访问器set和get可以以任何顺序声明,并且,除了这两个访问器外在属性上不允许有其他方法。
使用属性
写入和读取属性的方法与访问字段一样。访问器被隐式调用。
- 要写入一个属性,在赋值语句的左边使用属性的名称。
- 要读取一个属性,把属性的名称用在表达式中。
属性和关联字段
惯例是在类中将字段声明为private以封装一个字段,并声明一个public属性以提供受控的从类外部对字段的访问。和属性关联的字段常被称为后备字段或后备存储。
属性和它们的后备字段有几种命名约定。一种约定是两个名称使用相同的内容,但字段使用Camel大小写(首字母小写),属性使用Pascal大小写。虽然这违反了“仅使用大小写区分不同标识符是个坏习惯”这条普通规则,但它有个好处,可以把两个标识符以一种有意义的方法联系在一起。另一种约定是属性使用pascal大小写,对于字段,使用Camel大小写版的相同标识符,并以下划线开始。
执行其他计算
属性访问器并不局限于仅仅对关联的后备字段传进传出数据。访问器get和set能执行任何计算,或不执行任何计算。唯一必须的行为是get访问器要返回一个属性类型的值。
只读和只写属性
可以通过忽略访问器的声明,以使一个或其他的属性访问器不被定义。
- 只有get访问器的属性称为只读属性。只读属性是一种安全的方法,把一项数据从类或类的实例中传出,而不允许太多访问。
- 只有set访问器的属性称为只写属性。只写属性是把一项数据从类的外部传入类而不允许太多访问的安全方法。
- 两个访问器中至少有一个必须定义,否则编译会产生一条错误信息。
- 不声明后备字段—编译器根据属性的类型分配存储。
- 不能提供访问器的方法体—它们被简单地声明为分号。get担当简单的内存读,set担当简单的写。
- 除非通过访问器,否则不能访问后备字段,因为不能用其他的方法访问它,所以只读和只写自动实现属性也就没有意义,因此它们不被允许。
静态属性
属性也可以声明为static。静态属性的访问器和所有静态成员一样:
- 不能访问类的实例成员—虽然它们能被实例成员访问。
- 存在,不管类是否有实例。
- 当从类的外部访问时,必需使用类名引用,而不是实例名。
6.9 实例构造函数
实例构造函数是一个特殊的方法,它在类的每个新实例创建的时候执行。
- 构造函数用于初始化类实例的状态。
- 如果希望能从类的外部创建类的实例,需要声明构造函数为public。
- 构造函数的名称和类名相同
- 构造函数不能有返回值
- 构造函数可以带参数。参数的语法和其他方法完全相同。
- 构造函数可以被重载
在使用创建对象表达式创建类的新实例时,要提供new运算符,并跟着类的构造函数之一。new运算符使用该构造函数创建类的新实例。
默认构造函数
如果在类的声明中没有显式地提供实例构造函数,那么编译器会提供一个隐式的默认构造函数,它有以下特征:
- 它不带参数。
- 它的方法体为空。
构造函数也可以声明为static。实例构造函数初始化类的每个新实例,而static构造函数初始化类层次的项目。通常,静态构造函数初始化类的静态字段。
- 类层次的项目需要被初始化:
- 在任何静态成员被引用之前。
- 在类的任何实例被创建之前。
- 静态构造函数在下列方面就像实例构造函数:
- 静态构造函数的名称必须和类名相同
- 构造函数不能返回值。
- 静态构造函数在下列方面和实例构造函数不同:
- 静态构造函数声明中使用static关键字。
- 类只能有一个静态构造函数,而且不能带参数。
- 静态构造函数不能有访问修饰符。
- 类层次的项目需要被初始化:
- 类即可以有静态构造函数也可以有实例构造函数。
- 如同静态方法,静态构造函数不能访问所在类的实例成员,因此也不能使用this访问器。
- 不能从程序中显式调用静态构造函数,它们被系统自动调用。
- 在类的任何实例被创建之前。
- 在类的任何静态成员被引用之前。
- 被初始化的成员在创建对象的代码中必须是可访问的(如public)。
- 初始化在构造函数完成之后发生。
析构函数(destructor)执行在类的实例被销毁之前需要的清理或释放非托管资源的行为。
关于析构函数的重要内容如下:
- 每个类只能有一个析构函数。
- 析构函数不能带参数。
- 析构函数不能带访问修饰符。
- 析构函数和类有相同的名称,但以一个“~”字符作前缀。
- 析构函数只对类的实例起作用,因此没有静态析构函数。
- 不能在代码中显式地调用析构函数。相反,它在垃圾收集过程中调用,当时垃圾收集器分析代码,并确定在代码中没有任何途径引用该对象。
- 如果不需要,就不要执行析构函数,它们会带来性能上的开销。
- 析构函数只应释放对象自己的外部资源,它不应该访问其他的对象,因为无法假定这些对象还没有被收集。
调用析构函数
与C++析构函数不同,C#析构函数并不在实例失效时立即调用。事实上,无法知道析构函数会在什么时候调用。此外,如前所述,也不能显式地调用析构函数。如果你的代码需要析构函数,你就必须为系统提供它,系统会在对象从托管的堆中移走之前的某点调用它。
如果你的代码中包含需要及时清理的非托管资源,别把它留给析构函数处理,因为不能保证析构函数会很快运行。相反,你应该采用标准模式,在为中实现名称为IDisposable的接口。接口中把资源的清理代码封装在一个void型的无参数方法中,该方法应该叫做Dispose。
当你使用完资源并想释放他们时,你需要调用Dispose。注意,是你调用Dispose,而不是析构函数。系统不会为你自动调用它。
关于Dispose方法的一些指导方针如下:
- 以一种方法实现Dispose中的代码,使该方法被不止一次调用时也是安全的。如果它已经被调用过了,那么接下来的任何调用都不应该产生异常或者任何附加工作。
- 把Dispose方法和析构函数编写成这样,如果因为某种原因,你的代码没有调用Dispose,你的析构函数应该调用它并释放资源。
- 因为是Dispose做还是而不是析构函数,所以它应当调用GC.SuppressFinalize方法,该方法告诉CLR不要调用该方法的析构函数,因为它已经被清理了。
调用时间和调用频度 | ||
---|---|---|
实例 | 构造函数 析构函数 | 在创建类的每个新实例时调用一次 对类的每个实例调用,在程序不再访问该实例之后的某一时间点 |
静态 | 构造函数 析构函数 | 只调用一次,在类的任意静态变量第一次被访问之前,或在类的任何实例被创建之前,无论两者谁先发生。 不存在,只有实例才有析构函数 |
6.12 readonly修饰符
字段可以用readonly修饰符声明。其作用类似于声明一个字段为const,一但值被设定就不能改变。
- const字段只能在字段的声明语句中初始化,而readonly字段可以在下列任意位置设置它的值:
- 字段声明语句,如同const。
- 类的任何构造函数。如果是static字段,初始化必须在static构造函数中这城。
- const字段的值必须在编译期决定,而readonly字段的值可以在运行期决定。这种增加的自由性允许你在不同的构造函数中设置不同的值。
- 和const不同,const总是像静态的,而对于readonly字段:
- 它可以是实例字段,也可以是静态字段。
- 它在内存中有存储位置。
6.13 this关键字
this关键字在类中使用,是对当前实例的引用。它只能被用在下列类成员的代码块中:
- 实例构造函数。
- 实例方法。
- 属性和索引的实例访问器。
很明显,因为静态成员不是实例的一部分,所以不能在任何静态函数成员的代码中使用this关键字。更适当地说,this被用于下列目的:
- 用于区分类的成员和本地变量或参数。
- 作为调用方法的实参。
6.14 索引
有的时候,如果能使用索引访问实例字段将会很方便,好像该实例是字段的数组一样。
什么是索引
索引是一组get和set访问器,类似于属性的访问器。
格式:
string this [index index]
{
set
{
}
get
{
}
}
索引和属性
索引和属性在很多方面是相似的。
- 和属性一样,索引不用分配内存来存储。
- 索引和属性都主要被用来访问其他数据成员,这些成员和它们关联,它们为这些成员提供设置和获取访问。
- 属性通常访问单独的数据成员。
- 索引通常访问多个数据成员。
- 索引可以只有一个访问器,也可以两个都有。
- 索引总是实例成员。因此,索引不能被声明为static。
- 和性一样,实现get和set访问器的代码不必一定要关联到某个字段或属性。这段代码可以做任何事情或什么也不做,只要get访问器返回某个指定类型的值即可。
- 索引没有名称。在名称的位置是关键字this。
- 参数列表的方括号中间。
- 参数列表中至少必须声明一个参数。
声明索引类似于声明属性:
- 和属性不同,索引有一个参数列表,在方括号中,不可或缺;并使用this引用,而不是名称。
- 和属性一样,索引,有一个返回类型;有set和get访问器。
- 一个隐式参数,名称为value,value持有要保存的数据。
- 一个或更多索引参数,表示数据应该保存到哪里。
- 它的返回类型为void。
- 它使用的参数列表和索引声明中的相同。
- 它有一个名value的隐式值参,值参类型和索引类型相同。
- 它的参数列表和索引声明中的相同。
- 它返回与索引相同类型的值。
- 仅当成员(属性或索引)即有get访问器也有set访问器时,其访问器才能有访问修饰符。
- 虽然两个访问器都必须出现,但它们中只能有一个有访问修饰符。
- 访问器的访问修饰符必须比成员的访问级别有更严格的限制性。
- 每个分部类的声明都含有一些类成员的声明。
- 类的分部类声明可以在同一文件中也可以在不同文件中。
- 定义声明给出签名和返回类型,声明的实现部分只是一个分号。
- 实现声明给出签名、返回类型,还有正常形式的语句块实现。
- 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:
- 在定义声明和实现声明中都必须包含上下文关键字partial,直接放在关键字void之前。
- 签名不能包括访问修饰符,这使分部方法是隐式私有的。
- 返回类型必须是void。
- 参数列表不能包含out参数。
- 可以有定义部分而没有实现部分。在这种情况下,编译器把方法的声明以及方法内部任何对方法的调用都移除。然而,如果类有分部方法的实现部分,它也必须有定义部分。
- 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征: