C# 类和继承(构造函数的执行)
构造函数的执行
在前一章中,我们看到了构造函数执行代码来准备一个即将使用的类。这包括初始化类的静
态成员和实例成员。在这一章,你会看到派生类对象有一部分就是基类对象。
- 要创建对象的基类部分,需要隐式调用基类的某个构造函数。
- 继承层次链中的每个类在执行它自己的构造函数体之前执行它的基类构造函数。
例如,下面的代码展示了类MyDerivedClass及其构造函数声明。当调用该构造函数时,它在
执行自己的方法体之前会先调用无参数的构造函数MyBaseClass()。
class MyDerivedClass:MyBaseClass
{MyDerivedClass() //构造函数调用基类构造函数MyBaseClass(){...}
}
构造的顺序如图8-11所示。创建一个实例的过程中,完成的第一件事是初始化对象的所有
实例成员。在此之后,调用基类的构造函数,然后才执行该类自己的构造函数体。
例如,在下面的代码中,MyField1和MyField2的值在调用基类构造函数之前会分别设置为5
和0。
class MyDerivedClass:MyBaseClass
{int MyField1=5; //成员初始化int MyField2; //成员初始化public MyDerivedClass() //执行构造函数体{...}
}class MyBaseClass
{public MyBaseClass() //调用基类构造函数{...}
}
警告 强烈反对在构造函数中调用虚方法。在执行基类的构造函数时,基类的虚方法会调用派
生类的覆写方法,但这是在执行派生类的构造函数方法体之前。因此,调用会在派生类
完全初始化之前传递到派生类。
构造函数初始化语句
默认情况下,在构造对象时,将调用基类的无参数构造函数。但构造函数可以重载,所以基
类可能有一个以上的构造函数。如果希望派生类使用一个指定的基类构造函数而不是无参数构造
函数,必须在构造函数初始化语句中指定它。
有两种形式的构造函数初始化语句。
- 第一种形式使用关键字base并指明使用哪一个基类构造函数。
- 第二种形式使用关键字this并指明应该使用当前类的哪一个构造函数。
基类构造函数初始化语句放在冒号后面,跟在类的构造函数声明的参数列表后面。构造函数
初始化语句由关键字base和要调用的基类构造函数的参数列表组成。
基类构造函数初始化语句放在冒号后面,跟在类的构造函数声明的参数列表后面。构造函数
初始化语句由关键字base和要调用的基类构造函数的参数列表组成。
例如,下面的代码展示了类MyDerivedClass的构造函数。
- 构造函数初始化语句指明要使用有两个参数的基类构造函数,并且第一个参数是一个
int,第二个参数是一个string。 - 基类参数列表中的参数必须在类型和顺序方面与已定的基类构造函数的参数列表相匹配。
当声明一个不带构造函数初始化语句的构造函数时,它实际上是带有base()构造函数初始化
语句的简写形式,如图8-12所示。这两种形式是语义等价的。
另外一种形式的构造函数初始化语句可以让构造过程(实际上是编译器)使用当前类中其他
的构造函数。例如,如下代码所示的myclass类包含带有一个参数的构造函数。但这个单参数的
构造函数使用了同一个类中具有两个参数的构造函数,为第二个参数提供了一个默认值。
这种语法很有用的另一种情况是,一个类有好几个构造函数,并且它们都需要在对象构造的
过程开始时执行一些公共的代码。对于这种情况,可以把公共代码提取出来作为一个构造函数,
被其他所有的构造函数用作构造函数初始化语句。由于减少了重复的代码,实际上这也是推荐的
做法。
你可能会觉得还可以声明另外一个方法来执行这些公共的初始化,并让所有构造函数来调用
这个方法。由于种种原因这不是一个好办法。首先,编译器在知道方法是构造函数后能够做一些
优化。其次,有些事情必须在构造函数中进行,在其他地方则不行。比如之前我们学到的readonly
字段只可以在构造函数中初始化。如果尝试在其他方法(即使这个方法只被构造函数调用)中
初始化一个readonly字段,会得到编译错误。不过要注意,这一限制仅适用于readonly字段,
不适用于readonly属性。
回到公共构造函数,如果这个构造函数可以用作一个有效的构造函数,能够初始化类中所有
需要初始化的东西,那么完全可以把它设置为public的构造函数。
但是如果它不能完全初始化一个对象怎么办?此时,必须禁止从类的外部调用构造函数,因
为那样的话它只会初始化对象的一部分。要避免这个问题,可以把构造函数声明为private,而
不是public,然后只让其他构造函数使用它,如以下代码所示:
class MyClass
{readonly int firstVar;readonly double secondVar;public string UserName;public int UserIdNumber;private MyClass() //私有构造函数执行其他构造{ //函数共用的初始化firstVar=20;secondVar=30.5;}public MyClass(string firstName):this //使用构造函数初始化语句{UserName=firstName;UserIdNumber=-1;}public MyClass(int idNumber):this() //使用果子函数初始化语句{UserName="Annonymous";UsserIdNumber=idNumber;}
}
类访问修饰符
类可以被系统中其他类看到并访问。这一节阐述类的可访问性。虽然我们会在解说和示例中使用类,因为类是本书中一直阐述的内容,但可访问性规则也适用于以后将阐述的其他类型。
可访问(accessible)有时也称为可见(visible),它们可以互换使用。类的可访问性有两个
级别:public和internal。
- 标记为public的类可以被系统内任何程序集中的代码访问。要使一个类对其他程序集可
见,使用public访问修饰符,如下所示:
- 标记为internal的类只能被它自己所在的程序集内的类看到。(第1章介绍过,程序集既
不是程序也不是DLL。第22章将阐述程序集的细节。) - 这是默认的可访问级别,所以,除非在类的声明中显式地指定修饰符public,否则程
序集外部的代码不能访问该类。 - 可以使用internal访问修饰符显式地声明一个类为内部的。
图8-13阐明了internal和public类从程序集的外部的可访问性。类MyClass对左边程序集
内的类不可见,因为myClass被标记为internal。然而,类OtherClass对于左边的类可见,因为
它被标记为public。