06. 类进阶

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的类成员类型有:

  • 数据成员(存储数据):字段。
  • 函数成员(执行代码):方法、属性、构造函数、运算符、事件
6.6 成员变量
声明语法: const 类型 常量名=初始值;
与C和C++不同,在C#中没有全局常量。每个常都都必须声明在类型内。

成员常比本地常量更有趣,它们表现得像静态变量。它们对类的每个实例都是“可见的”,而且即使没有类的实例它们也可以使用。

然而,与真正的静态量不同,常量没有自己的存储位置,而是在编译时被编译器替换。这种方法类似于C和C++中的#define值。因此,虽然常量成员表现像一个静态变量,但不能声明一个常量为static。


6.7 属性

属性是代表类的实例或类中的一个数据项的成员。

使用属性看起来非常像写入或读取一个字段,语法是相同的。从使用上无法区分它们。

就像字段,属性有如下特征:

  • 它是命名的类成员。
  • 它有类型。
  • 它可以被赋值和读取。
然而和字段不同,属性是一个函数成员。
  • 它不为数据存储分配内存。
  • 它执行代码。
属性是指定的一组两个匹配的、称为访问器的方法。
  • set访问器用于为属性赋值。
  • get访问器用于从属性获取值。
6.8 属性声明和访问器
set和get访问器有预定义的语法和语义。可以把set访问器想象成一个方法,带有单一参数“设置”属性的值。get访问器没有参数并从属性返回一值。
    • set访问器总是:
      • 一个单独的、隐式的值参,名称为value,与属性的类型相同。
      • 一个返回类型void。
    • get访问器总是:
      • 没有参数。
      • 一个与属性类型相同的返回类型。

属性声明的语法结构

set访问器中的隐式参数value是一个普通的值参。和其他值参一样,可以用它发送数据到方法体或这种情况中的访问器块。在块的内部,可以像普通变量那样使用value,包括对它赋值。

访问器的其他重点有:

    • get访问器的所有执行路径必须包含一条return语句,返回一个属性类型的值。
    • 访问器set和get可以以任何顺序声明,并且,除了这两个访问器外在属性上不允许有其他方法。

使用属性

写入和读取属性的方法与访问字段一样。访问器被隐式调用。

    • 要写入一个属性,在赋值语句的左边使用属性的名称。
    • 要读取一个属性,把属性的名称用在表达式中。
依据写入或读取属性,适当的访问器会被隐式调用,不能显式地调用访问器。试图这样做会产生一个编译错误。

属性和关联字段

惯例是在类中将字段声明为private以封装一个字段,并声明一个public属性以提供受控的从类外部对字段的访问。和属性关联的字段常被称为后备字段或后备存储。

属性和它们的后备字段有几种命名约定。一种约定是两个名称使用相同的内容,但字段使用Camel大小写(首字母小写),属性使用Pascal大小写。虽然这违反了“仅使用大小写区分不同标识符是个坏习惯”这条普通规则,但它有个好处,可以把两个标识符以一种有意义的方法联系在一起。另一种约定是属性使用pascal大小写,对于字段,使用Camel大小写版的相同标识符,并以下划线开始。

执行其他计算

属性访问器并不局限于仅仅对关联的后备字段传进传出数据。访问器get和set能执行任何计算,或不执行任何计算。唯一必须的行为是get访问器要返回一个属性类型的值。

只读和只写属性

可以通过忽略访问器的声明,以使一个或其他的属性访问器不被定义。

    • 只有get访问器的属性称为只读属性。只读属性是一种安全的方法,把一项数据从类或类的实例中传出,而不允许太多访问。
    • 只有set访问器的属性称为只写属性。只写属性是把一项数据从类的外部传入类而不允许太多访问的安全方法。
    • 两个访问器中至少有一个必须定义,否则编译会产生一条错误信息。
自动实现属性
因为属性经常被关联到后备字段,C# 3.0增加了自动实现属性(automatically implemented property 或 auto-implemented property),允许只声明属性而不声明后备字段。
自动实现属性的要求点下:
    • 不声明后备字段—编译器根据属性的类型分配存储。
    • 不能提供访问器的方法体—它们被简单地声明为分号。get担当简单的内存读,set担当简单的写。
    • 除非通过访问器,否则不能访问后备字段,因为不能用其他的方法访问它,所以只读和只写自动实现属性也就没有意义,因此它们不被允许。

静态属性

属性也可以声明为static。静态属性的访问器和所有静态成员一样:

    • 不能访问类的实例成员—虽然它们能被实例成员访问。
    • 存在,不管类是否有实例。
    • 当从类的外部访问时,必需使用类名引用,而不是实例名。

6.9 实例构造函数

实例构造函数是一个特殊的方法,它在类的每个新实例创建的时候执行。

  • 构造函数用于初始化类实例的状态。
  • 如果希望能从类的外部创建类的实例,需要声明构造函数为public。
构造函数的语法很像类声明中的其他方法,除了下面这几点:
  • 构造函数的名称和类名相同
  • 构造函数不能有返回值
带参数的构造函数
构造函数在下列方面和其他方法相似:
    • 构造函数可以带参数。参数的语法和其他方法完全相同。
    • 构造函数可以被重载

在使用创建对象表达式创建类的新实例时,要提供new运算符,并跟着类的构造函数之一。new运算符使用该构造函数创建类的新实例。

默认构造函数

如果在类的声明中没有显式地提供实例构造函数,那么编译器会提供一个隐式的默认构造函数,它有以下特征:

    • 它不带参数。
    • 它的方法体为空。
如果程序员定义了一个或多个构造函数,那么编译器将不会为该类定义默认构造函数。

静态构造函数
构造函数也可以声明为static。实例构造函数初始化类的每个新实例,而static构造函数初始化类层次的项目。通常,静态构造函数初始化类的静态字段。
    • 类层次的项目需要被初始化:
      • 在任何静态成员被引用之前。
      • 在类的任何实例被创建之前。
    • 静态构造函数在下列方面就像实例构造函数:
      • 静态构造函数的名称必须和类名相同
      • 构造函数不能返回值。
    • 静态构造函数在下列方面和实例构造函数不同:
      • 静态构造函数声明中使用static关键字。
      • 类只能有一个静态构造函数,而且不能带参数。
      • 静态构造函数不能有访问修饰符。
关于静态构造函数应该了解的其他重要内容如下:
    • 类即可以有静态构造函数也可以有实例构造函数。
    • 如同静态方法,静态构造函数不能访问所在类的实例成员,因此也不能使用this访问器。
    • 不能从程序中显式调用静态构造函数,它们被系统自动调用。
      • 在类的任何实例被创建之前。
      • 在类的任何静态成员被引用之前。
构造函数的可访问性
可以为实例构造函数指派访问修饰符,就像对其他成员所做的那样。构造函数被声明为public,这样可以从类的外部创建实例。也可以创建private构造函数,它不能从类的外部调用,但可以从类的内部调用。

6.10 对象初始化列表
对象初始化列表允许在创建新的对象实例时设置字段和属性的值。
对象创建表达式由关键字new后面跟着一个类构造函数组成。对象初始化列表扩展了创建语法,在表达式的属部放置了一组成员初始化列表。

关于对象初始化列表的重要内容如下:
    • 被初始化的成员在创建对象的代码中必须是可访问的(如public)。
    • 初始化在构造函数完成之后发生。
6.11 析构函数

析构函数(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访问器。
set访问器
当索引被用于赋值时,set访问器被调用,并接受两项数据,如下:
    • 一个隐式参数,名称为value,value持有要保存的数据。
    • 一个或更多索引参数,表示数据应该保存到哪里。
在set访问器中的代码必须检查索引参数,以确定数据应该存往何处,然后保存它。
set访问器有如下语义:
    • 它的返回类型为void。
    • 它使用的参数列表和索引声明中的相同。
    • 它有一个名value的隐式值参,值参类型和索引类型相同。
get访问器
当索引被用于获取值时,get访问器被一个或多个索引参数调用。索引参数指标哪个值将被获取。
get访问器方法体内的代码必须检查索引参数,确定它表示的是哪个字段,并返回该字段的值。
get访问器有如下语义:
    • 它的参数列表和索引声明中的相同。
    • 它返回与索引相同类型的值。
关于索引的补充
和属性一样,get和set访问器不能被显示调用。取而代之,当索引被用在表达式中取值时,get访问器自动被调用。当索引被使用赋值语句赋值时,set访问器自动被调用。
当索引被“调用”时,参数在方括号中间提供。

索引重载
只要索引的参数列表不同,类可以有不止一个索引。索引类型不同是不够的。这叫做索引重载,因为所有的索引都有相同的“名称”:this访问引用。
记住,类中重载的索引必须有不同的参数列表。

访问器的访问修饰符
默认情况下,成员的两个访问器有和成员自身相同的访问级别。也就是说,如果一个属性有public访问级别,那么它的两个访问器都有同样的访问级别,对索引也一样。
在特定情况下,成员的访问器可以有不同的访问级别。例如,属性Name有public访问级别,但set访问器有protected访问级别。
class MyClass
{
private string _Name="John Doe";
public string Name
{
get {return _Name;}
protected set {_Name = value;}
}
}
访问器的访问修饰符有几个限制。最重要的限制如下:
    • 仅当成员(属性或索引)即有get访问器也有set访问器时,其访问器才能有访问修饰符。
    • 虽然两个访问器都必须出现,但它们中只能有一个有访问修饰符。
    • 访问器的访问修饰符必须比成员的访问级别有更严格的限制性。
6.15 分部类和分部类型
类的声明可以被分割成几个分部类的声明。
  • 每个分部类的声明都含有一些类成员的声明。
  • 类的分部类声明可以在同一文件中也可以在不同文件中。
每个局部声明必须被标为 partial class,而不是单独的关键字class。分部类声明看起来和普通类声明相同,除了那个附加的类型修饰符partial

组成类的所有分部类声明必须在一起编译。使用分部类声明的类必须有相同的含义,如同它所有类成员都被声明在一个单独的类声明体内。
除了类,还可以创建两个分部类型:局部结构和局部接口。

分部方法
分部方法是在分部类中声明在两个部分中的方法。分部方法的那两个部分可以声明在分部类的不同部分,也可以声明在同一部分。分部方法的两个部分如下。
    • 定义声明给出签名和返回类型,声明的实现部分只是一个分号。
    • 实现声明给出签名、返回类型,还有正常形式的语句块实现。
关于分部方法需要了解的重要内容如下。
    • 定义声明和实现声明的签名和返回类型必须匹配。签名和返回类型有如下特征:
      • 在定义声明和实现声明中都必须包含上下文关键字partial,直接放在关键字void之前。
      • 签名不能包括访问修饰符,这使分部方法是隐式私有的。
      • 返回类型必须是void。
      • 参数列表不能包含out参数。
    • 可以有定义部分而没有实现部分。在这种情况下,编译器把方法的声明以及方法内部任何对方法的调用都移除。然而,如果类有分部方法的实现部分,它也必须有定义部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值