类——一种数据结构,前面提到过很多次,这次就详细写一下。类可以包含数据成员(常数和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数、析构函数),以及嵌套类型。类类型支持继承,继承是一种机制,使得派生类可以对基类进行扩展和专用化。
10.1类声明
类声明是一种类型声明,用于声明一个新的类。
上面这两个图就是类声明的组成方式:先是一组可选的特性,后跟可选的类修饰符,接着是关键字class和用来命名该类的标识符(即该类的名字),然后是可选的类基规范,最后是类体,后面可能接着一个分号。
10.1.1 类修饰符
new修饰符:适用于嵌套类,它表示所修饰的类会把继承下来的同名成员隐藏起来。若new出现在类声明中,却又不是一个嵌套类声明,会导致编译时错误。
public、protected、internal、private修饰符是控制类的可访问性,第三章具体写过,这里不多写了。
abstract修饰符:表示所修饰的类是不完整的,并且它只能用做基类,且用它修饰的类被称为抽象类。抽象类不能直接实例化,并且对抽象类使用new运算符会导致编译时错误。当抽象类派生出非抽象类时,非抽象类必须具体实现继承的所有抽象类成员。
sealed修饰符:用于防止所修饰的类派生出其他类,所以也被称为密封类。密封类不能为抽象类。
10.1.2 类基规范
类基规范:定义了该类的直接基类和由该类实现的接口。
如果类声明中没有类基,或所含的类基只列出接口类型,那就设定直接基类是object,类会从它的直接基类继承成员。类类型的直接基类必须至少与该类具有同样的可访问性,如果试图从private类派生一个public类,就会导致编译时错误。
类的基类包括它的直接基类和直接基类的基类。可以说基类集是直接基类关系的传递闭包。比如:class A{} class B:A{} B的基类是A和object。
10.1.3 类体
类体:用于定义该类的成员。
10.2类成员
类成员分两部分组成:自己声明引入的成员和从直接基类继承来的成员。
常数:表示与该类相关联的常数值;
字段:该类的变量;
方法:实现由该类执行的计算和操作;
属性:用于定义一些命名特性,以及与读取和写入这些特征相关的行为;
事件:用于定义可由该类生成的通知;
索引器:使该类的实例可按与数组相同的方式进行索引;
运算符:用于定义表达式运算符,通过它对该类的实例进行运算;
实例构造函数:用于规定在初始化该类的实例时需要做什么;
析构函数:用于规定在永远地放弃该类的实例之前需要做什么;
静态构造函数:用于规定在初始化该类自身时需要做什么;
类型:用于表示一些类型,它们是该类的局部类型。
可包含可执行代码的成员统称为该类的函数成员。类的函数成员包括:方法、属性、事件、索引器、运算符、实例构造函数、析构函数、静态构造函数。
类声明将创建新的声明空间,而直接在该类声明内的类成员声明将在此声明空间引入新成员:
实例构造函数、析构函数和静态构造函数必须具有和直接封闭它们的类同样的名称,其他的成员的名称必须和该类的名称不同;
常数、字段、属性、事件或类型的名称必须不同于在同一个类中声明的其他成员的名称;
方法的名称必须和其他在同一个类声明的所有非方法的名称不同,此外方法的签名也不能和其他方法相同;
实例构造函数、索引器和运算符的签名必须不同于同一类的其他所有的实例构造函数、索引器和运算符。
注:类的继承成员不再类的声明空间的组成部分,所以派生类可以使用同样的名称或签名来声明自己的新成员,同时隐藏被继承的同名成员。
10.2.1 继承
类继承它的直接基类的成员。继承以为着隐式地把它的直接基类的所有成员当做自己的成员,当然除了基类的实例构造、静态构造和析构函数除外。
继承是可传递的。比如A派生B,B又派生C,那么C就会继承A、B中的声明成员;
派生类扩展它的直接基类,可以向它继承的成员添加新成员,但不能移除继承成员的定义;
除了上面说的3个例外,所有都可被继承,但有的可能不能被访问,这就和访问性有关了;
类可以声明虚拟方法、属性和索引器,而派生类可以重谢它们的实现,这就展示了多态特征。
10.2.2 new修饰符
类成员声明中可以使用与一个被继承的成员相同的名称或签名来声明一个成员,这就称该派生类成员隐藏了基类成员。隐藏不算错误,但会发出警告,这时就可以在派生类成员的声明中包含一个new修饰符,表示有意隐藏基类成员就可以了。
10.2.3 访问修饰符
类成员的访问性可以有五种:public、protected internal、protected、internal和private,默认时是private。
10.2.4 静态成员和实例成员
类的成员要么是静态成员,要么就是实例成员。静态成员属于类,实例成员属于对象(即类的实例)。
当字段、方法、属性、事件、运算符和构造函数声明中含有static修饰符时,就是声明了静态成员。常数和类型会隐式地声明为静态成员;当没有static修饰符时,就是实例成员。
10.2.6 嵌套类型
在类或结构内声明的类型称嵌套类型;在编译单元或命名空间声明的类型是非嵌套类型。比如:
类B就是嵌套类型,因为它声明在A中;相对A就是非嵌套类型。嵌套类型的完全限定名为S.N,比如上面的B的完全限定名就是:A.B。
非嵌套类型只能具有2个访问类型,public、internal,默认是internal。
10.2.7 保留成员名称
为了便于底层的C#运行库的实现,对于每个属性、事件或索引器的源成员声明,任何一个实现都必须根据该成员声明的种类、名称和类型保留两个方法签名。如果程序声明一个成员,而该成员的签名与这些保留签名中的某一个匹配,那么即使所使用的底层运行库的实现并没有使用这些保留签名,但仍会导致一个编译时错误。
保留名称不会引入声明,因此它们不参与成员查找。但是,一个声明若具有相关联的保留方法签名,则该方法签名会参与继承,也可以被隐藏。保留这么名称的目的:
1.使基础的实现可以通过将普通标识符用做一个方法名称,对C#语言功能进行get或set访问;
2.使其他语言可以通过将普通标识符用做一个方法名称,对C#语言功能进行get或set访问,从而实现交互操作;
3.使保留成员名称的细节在所有C#实现中保持一致,这有助于确保被一个符合本规范的编译器所接受的源程序也可被另一个编译器接受。
对于类型T的属性P,保留了下列签名:
对于委托类型T的事件E,保留了下列签名:
对于类型T的具有参数列表L的索引器,保留了下列签名:
对于包含析构函数的类,保留了下列签名:
10.3常数
常数是类成员,表示一个常数值(可以在编译时计算的值)。常数声明可以引入一个或多个给定类型的常数:
虽然常数被认为是静态成员,但在常数声明中即不要求也不允许使用static修饰符。
常数声明中指定的类型必须是sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool、string、枚举类型或引用类型。每个常数表达式所产生的值,必须属于目标类型的,或者可以通过隐式转换转换为目标类型。
如果需要一个具有常数值的符号名称,但该值的类型不允许在常数声明中使用,或在编译时无法有常数表达式计算出该值,则可以改用readonly字段。
10.4字段
字段:是一种表示与对象或类关联的变量的成员。
10.4.1 静态字段和实例字段
当字段声明中含有static修饰符时,由该声明引入的字段为静态字段;当不存在时,由该声明引入的字段为实例字段。静态字段和实例字段是C#所支持的几种变量中的两种,它们有时被分别称为静态变量和实例变量。
10.4.2 只读字段
当字段声明中还有readonly修饰符时,该声明所引入的字段为只读字段。给只读字段的直接赋值只能作为声明的组成部分出现,或在同一类的实例构造函数或静态构造函数中出现;对于实例字段,在包含字段声明的类的实例构造函数中,对于静态字段,在包含字段声明的类的静态构造函数中,将readonly字段作为out或ref参数传递才有效。
静态只读字段:需要一个具有常数值的符号名称,但该值的类型不允许在const声明中使用,或这无法在编译时计算出该值,则static readonly字段就可以发挥作用了。比如:
其中,Black、White、Red、Green、Blue成员不能被声明为const成员,这是因为编译时无法计算它们的值。
常数和只读字段具有不同的二进制版本控制语义。当表达式引用常数时,该常数的值在编译时获取,但当表达式引用只读字段时,要等到运行时才获取该字段的值。比如:
Program1和Program2命名空间表示两个单独编译的程序。由于Program1.Utiles.X声明为静态只读字段,因此Consele.WriteLine语句要输出的值在编译时是未知的,在运行时才能获取。如果更改X的值并重新编译Program1,则即使Program2未被重新编译,Consele.WriteLine语句也将输出新值。但如果X是常数,那么X的值将在编译Program2时获取,并且在重新编译Program2之前不会受到Program1中的更改的影响。
10.4.3 易失字段
当字段声明中含有volatile修饰符时,该声明引入的字段为易失字段。
由于采用了优化技术(它会重新安排指令的执行顺序),在多线程的程序运行环境下,如果不采取同步控制手段,则对于非易失字段的访问可能会导致意外的和不可预见的结果。这些优化可以由编译器、运行时系统或硬件执行。但对于易失字段,优化时的这种重新排序必须遵循以下规则:
1.读取一个易失字段称为易失读取。易失读取具有"获取语义",也就是说,按照指令序列,所有排在易失读取之后的对内存的引用,在执行时也一定排在它的后面;
2.写入一个易失字段称为易失写入。易失写入具有"释放语义",也就是说,按照指令序列,所有排在易失写入之前的对内存的引用,在执行时也一定排在它的面前。
这些限制能确保所有线程都会观察到由其他任何线程所执行的易失写入(按照原来安排的顺序)。一个遵循本规范的实现并非必须要使易失写入的执行顺序,在所有正在执行的线程看来都是一样的。易失字段的类型必须时下列类型中的一种:
引用类型;
类型byte,sbyte,short,ushort,int,uint,char,float或bool;
枚举基类型为byte,sbyte,short,ushort,int或uint的枚举类型。
10.4.4 字段初始化
字段的初始值都是字段的类型的默认值。在此默认初始化发生之前是不可能看到字段的值的,因此字段永远不会是"未初始化的"。比如:
这是因为b和i都被自动初始化为默认值。
10.4.5 变量初始值设定项
字段声明可以包含变量初始值设定项。对于静态字段,变量初始值设定项相当于在类初始化期间执行的赋值语句;对于实例字段,变量初始值设定项相当于创建类的实例时执行的赋值语句。
10.5方法
方法:一种用于实现可以由对象或类执行的计算或操作的成员。
注:如果该声明包含了abstract修饰符,则该声明不包含下列任何修饰符:static,virtual,sealed或extern;
如果声明包含private修饰符,则该声明不包含下列任何修饰符:virtual,override或abstract;
如果声明包含sealed修饰符,则该声明还包含override修饰符。
10.5.1 方法参数
一个方法的参数是由该方法的形参来声明的。
形参表包含一个或多个由逗号分隔的参数,其中只有最后一个参数才可以是参数数组。
方法声明为所声明的参数和局部变量创建了单独的声明空间。该方法的形参表和在方法的块中的局部变量声明把它们所声明的名称提供给此声明空间。
执行方法时调用时,创建关于该方法的形参列表和局部变量的一个副本(仅供本次调用),而该调用所提供的参数列表则用于把所含的值或变量引用赋给新创建的形参。
10.5.2 静态方法和实例方法
若一个方法声明中含有static修饰符,则称静态方法;若没有则称实例方法。
10.5.3 虚拟方法
若一个实例方法的声明中包含virtual修饰符,则称为虚拟方法。相对的非虚拟方法无论在类还是派生类,实现都是相同的,而虚拟方法不是,它可以由派生类取代,而取代所继承虚拟方法的实现过程被称为重写该方法。
在虚拟方法调用中,该调用所涉及的那个实例的运行时类型确定要被调用的究竟是该方法的哪一个实现。在非虚拟方法调用红,相关的实例的编译时类型是决定性因素。比如:
上面的F方法为非虚拟方法,所以由编译时类型确定调用哪个方法实现。a的编译时类型是A,所以调用A类中的F方法,同样b调用B中的F方法。
G方法为虚拟方法,所以由运行时类型确定调用哪个方法实现。a的运行时类型是B,所以调用B类中的G方法,同样b也调用B类中的G方法。
10.5.4 重写方法
若一个实例方法声明中包含override修饰符,则称该方法为重写方法,由override声明重写的方法称已重写的基方法。重写方法用相同的签名重写所继承的虚拟方法。但是要注意,重写声明和已重写的基方法具有相同的返回类型和声明可访问性。
10.5.5 密封方法
当实例方法声明包含sealed修饰符时,则称该方法为密封方法。如果实例方法声明包含sealed修饰符,则它必须包含override修饰符。
10.5.6 抽象方法
当实例方法声明包含abstract修饰符时,称该方法为抽象方法。虽然抽象方法同时隐含虚拟方法,但它不能由virtual修饰符。抽象方法声明引入一个新的虚拟方法,但不提供该方法的实现;由继承它的非抽象派生类重写该方法以提供它们自己的实现。由于抽象方法不提供任何实际实现,所以抽象方法的方法体只包含一个分号。
切记,只允许在抽象类中使用抽象方法。
在抽象方法声明中可以重写虚拟方法,这使一个抽象类可以强制在它的派生类中重新实现该方法,并使得原始实现不再可用。
10.5.7 外部方法
当方法声明包含extern修饰符时,称该方法为外部方法。外部方法就是在外部实现的,通常使用C#以外的编程语言。外部方法也不提供任何实际实现,所以方法体也是只包含一个分号。
extern修饰符通常与DllImport特性一起使用,从而使外部方法可以由DLL(动态链接库)实现。执行环境可以支持其他用来提供外部方法实现的机制。当外部方法包含DllImport特性时,该方法声明必须同时包含一个static修饰符。比如:
10.5.8 方法体
方法声明中的方法体:要么包含一个块,要么包含一个分号。比如抽象方法和外部方法不提供方法实现,所以它们的方法体只包含一个分号。
当方法的返回类型是void时,不允许该方法体中的return语句指定表达式,当正常执行完后,返回到调用它的地方;若不是void时,方法体中的每个return语句都必须指定一个可隐式转换为返回类型的类型表达式。
10.6属性
属性:一种用于访问对象或类的特性的成员。属性的示例包括字符串的长度、字体的大小、窗口的标题、用户的名称等等。属性是字段的自然扩展,两者都有关联类型的命名成员,而且访问字段和属性的语法是相同的;但是与字段不同的是属性不表示存储位置,相反属性有访问器,这些访问器指定在它们的值被读取或写入时执行。因此属性提供了一种机制,它把读取和写入对象的某些特性与一些操作关联起来;甚至还可以对此类特性进行计算。
对于有效的修饰符组合,属性声明与方法声明遵循相同的规则。属性声明中的类型用于指定该声明引入的属性类型,而成员名则指定该属性的名称。除非该属性是一个显示的接口成员实现,否则成员名就只是一个标识符。
10.6.1 静态属性和实例属性
包含static修饰符,被称为静态属性;相反称实例属性。
10.6.2 访问器
属性的访问器声明指定与读取和写入该属性相关联的可执行语句。
访问器声明由一个get访问器声明或一个set访问器声明,或者两者一起组成。对于abstract和extern属性,每个指定访问器的访问体都只是一个分号。get访问器相当于一个具有属性类型返回值的无参数方法,set访问器相当于一个具有单个属性类型值参数和void返回类型的方法。属性也可以被同名属性隐藏。
10.6.3 虚拟、密封、重写和抽象访问器
virtual属性声明指定属性的访问器是虚拟的,适用于读写两个访问器。注:读写访问器不能只有其中一个是虚拟的;
abstract属性声明指定属性的访问器是抽象的,同样不提供实际实现,抽象类的非抽象类的派生类必须重写;
声明若同时包含abstract和override修饰符,表示属性是抽象的并且重写一个基属性;
sealed属性声明指定属性是密封的;
除了声明和调用语法中的差异,和虚拟、密封、重写和抽象方法具有完全相同的行为。
10.7事件
事件:一种使对象或类能够提供通知的成员。客户端可以通过提供事件处理程序为相应的事件添加可执行代码。
第四章我们提到过,事件的类型必须是委托类型,而且委托的可访问性至少和事件本身一样。
事件声明中可以包含事件访问器声明,但如果没有,对于非外部、非抽象事件,编译器将自动提供;对于外部事件,访问器由外部提供。
若事件即包含abstract修饰符,又包含以括号分割的事件访问器声明,会导致编译时错误。
若事件声明包含extern修饰符,被称为外部事件。
事件可以做+=和-=运算符左边的操作数,这些运算符分别用于将事件处理程序添加到所涉及的事件或从该事件中移除事件处理程序。
由于+=和-=是仅有的能在声明某个事件的类型的外部对该事件进行的操作,因此外部代码可以为一个事件添加和移除处理程序,但不能以其他任何方式来获取或修改基础的事件处理程序列表。
10.7.1 类似字段的事件
在包含事件声明的类或结果的程序文本中,某些事件可以像字段一样使用。这样的话,事件不能是abstract或extern,而且不能显示包含事件访问器声明。这样这个事件就可以当作字段用,意思是字段包含一个委托,它引用添加到相应事件的事件处理程序列表。若果尚未添加任何事件处理程序,该字段为null。比如:
Click在Button类中用作一个字段。可以在委托调用表达式中检查、修改和使用字段。Button类中的OnClick方法用于"引发"一个Click事件。"引发一个事件"与"调用一个由该事件表示的委托"完全等效,因此没有用于引发时间的特殊语言构造。注:在委托调用之前有一个检查,以确保该委托非空。
在Button类的声明外,Click成员只能用在+=和-=运算符的左边。如:
将一个委托追加到Click事件的调用列表
从Click事件的调用列表中移除一个委托
当编译一个类似字段的事件时,编译器自动创建一个存储区来存放相关的委托,并未事件创建相应的访问器以向委托字段中添加或移除事件处理程序。为了线程安全,添加或移除操作需在为实例事件的包含对象加锁的情况下进行,或者在为静态事件的类型对象加锁的情况下进行。比如:
可以编译为如下语句:
在类X中,引用Ev在编译时改为引用因此字段_Ev。名称"_Ev"是任意的,隐藏字段可以具有任何名称或根本没有名称。同样静态事件也一样:
10.7.2事件访问器
事件声明通常省略事件访问器,但在特殊情况,比如为每个事件设置一个字段所造成的内存开销,有时会变得不可接受。在这样的情况下可以在类中使用事件访问器声明,并采用专用机制来存储事件处理程序列表。
事件的事件访问器声明指定与添加和移除事件处理程序相关联的可执行语句。
访问器声明:由一个添加访问器声明和一个移除访问器声明组成。每个访问器声明都包含标记add或remove,后面跟一个块,块中是处理程序时要执行的语句。
每个添加访问器声明和移除访问器声明都相当于一个方法,它具有一个属于事件类型的值参数并且返回类型为void。
10.7.3 静态事件和实例事件
当事件包含static修饰符时,称静态事件;反之实例事件。
10.7.4 虚拟、密封、重写和抽象访问器
virtual事件声明指定事件的访问器是虚拟的,同样适用于事件的两个访问器。
abstract事件声明指定事件的访问器是虚拟的,同样不提供实际实现,通过派生的非抽象类重写。
同时包含abstract和override修饰符,表示事件是抽象的并重写一个基事件。
10.8索引器
索引器:使对象能用与数组同样的方式进行索引。始终是实例成员。
关于有效的修饰符组合,事件声明与方法声明遵循相同的规则,唯一例外的是:在索引器中不允许使用静态修饰符。
索引器声明的类型用于指定由该声明引入的索引器的元素类型。除非索引器是一个显示接口成员的实现,否则该类型后要跟一个关键字this;而对于显示接口成员的实现,该类型后面先跟一个接口类型、一个".",再跟一个关键字this。
与其他成员不同的是索引器不具有用户定义的名称。
形参表用于指定索引器的参数。索引器的参数表对应于方法的参数表,不同的仅仅在于索引器的形参表中必须至少含有一个参数,并且不允许使用ref和out参数修饰符。
访问器声明,必须被包括再"{}"标记被。这些访问器用来指定与读取和写入索引器元素相关联的可执行语句。
虽然访问索引器元素的语法和访问数组元素的语法相同,但索引器元素并不属于变量,所以不能将索引器元素作为ref或out参数传递。
索引器的形参表定义索引器的签名,所以索引器的签名由参数的数量和类型组成。因此索引器由它的签名标识。
10.9运算符
运算符:一种用来定义可应用于类的实例的表达式运算符含义的成员。
有三个类别的可重载运算符:一元运算符、二元运算符、转换运算符。
同样,当运算符声明包含extern修饰符时,称为外部运算符。
运算符的声明规则:1.运算符声明必须同时包含public和static修饰符;
2.运算符的参数必须是值参数,在运算符中指定ref或out参数会导致编译时错误;
3.运算符的签名不同于同一类的其他签名;
4.运算符声明中引用的所有类型必须至少具有和运算符一样的可访问性。
每个运算符还有各自的限制,下面会说明。
10.9.1 一元运算符
一元运算符声明规则:1.一元+、-、!或~运算符必须带有单个T类型的参数,并且可以返回任何类型;
2.一元++、--运算必须带有单个T类型的参数,且返回类型必须为T;
3.一元true、false运算符必须带有单个T类型的参数,且返回类型必须为bool。
注:T表示包含运算符声明的类或结构类型。true和false要求成对声明的,如果只声明其中一个,会发生编译时错误。
一元运算符的签名包含运算符标记和单个形参的类型。言外之意就是返回类型和形参名不是签名组成部分。
10.9.2 二元运算符
二元运算符必须带两个参数,其中至少有一个必须具有声明了该运算符的类或结构类型。二元运算符可以返回任何类型。
二元运算符的签名由运算符标记(+,-,*等)和两个形参的类型组成。同样返回类型和形参名不是签名的组成部分。
二元运算符也有要求成对声明的:==和!=、<和>、>=和<=。
10.9.3 转换运算符
转换运算符声明引入用户定义的转换:
包含implicit关键字的转换运算符声明引入用户定义的隐式转换;
包含explicit关键字的转换运算符声明引入用户定义的显式转换。
10.10实例构造函数
实例构造函数:是初始化类实例所需要操作的成员。
构造函数声明符中的标识符必须是该类的名称。
实例构造函数是不能被继承的。
10.10.1 构造函数初始值设定项
除了object类的实例构造函数外,所有其他的实例构造函数都隐式地包含一个对另一个实例构造函数的调用,该调用紧靠在构造函数体的面前。要隐式调用的构造函数是由构造函数初始值设定项确定的:
base形式的实例构造函数初始值设定项导致调用直接基类中的实例构造函数;
this形式的实例构造函数初始值设定项导致调用该类本身所声明的实例构造函数。
如果一个实例构造函数中没有构造函数初始值设定项,则会隐式添加一个base()形式的构造函数初始值设定项。比如:
实例构造函数声明中的形参表所给出的参数范围包含该声明的实例构造函数初始值设定项。比如:
实例构造函数初始值设定项不能访问正在创建的实例。因此在构造函数初始值设定项的参数表达式中引用this会导致编译时错误。
10.10.2 实例变量初始值设定项
如果实例构造函数没有构造函数初始值设定项,或仅具有base(...)形式的构造函数初始值设定项。该构造函数就会隐式地执行在该类中声明的实例字段的初始化操作,这些操作由对应的字段声明中的变量初始值设定项指定。这对应于一个赋值序列,它们会在进入构造函数时,在对直接基类的构造函数进行隐式调用之前立刻执行。这些变量初始值设定项按它们出现在类声明中的文本顺序执行。
10.10.3 构造函数执行
变量初始值设定项被转换为赋值语句,而这些语句将在对基类实例构造函数进行调用之前执行。这是为了在所有实例字段执行之前都已经按照它们的变量初始值设定项进行了初始化。
10.10.4 默认构造函数
刚才有提到,如果一个类不包含任何实例构造函数声明时,会自动为该类提供一个默认的实例构造函数。该默认实例构造函数只是调用直接基类的无参数构造函数,若直接基类没有无可访问的无参数实例构造函数,会报错。
10.10.5 私有构造函数
当类T只声明了私有实例构造函数时,在T的程序文本外部,既不可能从T派生出新的类,也不能直接创建T的任何实例。因此如果想要设计一个类,它只包含静态成员,而且不想使它被实例化,就可以添加一个空的私有实例构造函数。比如:
10.10.6 可选的实例构造函数参数
this(...)形式的构造函数初始值设定项通常与重载一起使用,以实现可选的实例构造函数。比如:
10.11静态构造函数
静态构造函数:一种用于实现初始化类所需操作的成员。
同样,静态构造函数的声明的标识符必须是该类的名称。
当声明包含extern修饰符时,被称为外部静态构造函数。
静态构造函数不可被继承,也不能被直接调用。且在给定应用程序域中之多执行一次。应用程序域中第一次发生以下事件时将触发静态构造函数的执行:创建类的实例、引用类的任何静态成员。
如果类中包含开始执行的Main方法,则静态构造函数将在调用Main方法之前执行。
10.12析构函数
析构函数:用于实现析构类实例所需操作的成员,它指定当销毁该类的一个实例时需要执行的语句,是完全具有void返回类型的实例方法的方法体。
析构函数的声明标识符也必须时该类的名称。
当析构函数声明包含extern修饰符时,被称为外部析构函数。
析构函数也不可能被继承;由于析构函数要求不带有参数,因此它不能被重载。
析构函数是被自动调用的,不能被显示调用。析构函数可以在任何线程上执行。销毁实例是,按照从派生程度最大到最小的顺序执行。
析构函数实际上是重写了System.Object中的虚方法Finalize。C#不允许重写此方法或直接调用它。