文章目录
-
- 8.1 类继承
- 8.2 访问继承的成员(*)
- 8.3 所有类都派生自 object 类
- 8.4 屏蔽基类的成员
- 8.5 基类访问
- 8.6 使用基类的引用
-
- 8.6.1 虚方法和覆写方法
- 8.6.2 覆写标记为 override 的方法
- 8.6.3 覆盖其他类型成员(*)
- 8.7 构造函数的执行
-
- 8.7.1 构造函数初始化语句
- 8.7.2 类访问修饰符
- 8.8 程序集间的继承
- 8.9 成员访问修饰符
-
- 8.9.1 访问成员的区域
- 8.9.2 公有成员的可访问性
- 8.9.3 私有成员的可访问性
- 8.9.4 受保护成员的可访问性
- 8.9.5 内部成员的可访问性
- 8.9.6 受保护内部成员的可访问性
- 8.9.7 成员访问修饰符小结
- 8.10 抽象成员
- 8.11 抽象类
-
- 8.11.1 抽象类和抽象方法的示例(*)
- 8.11.2 抽象类的另一个例子(*)
- 8.12 密封类
- 8.13 静态类
- 8.14 扩展方法
- 8.15 命名约定
8.1 类继承
- 要派生一个类,需要在类名后面加入 基类规格说明,由冒号和基类名称组成。
- 派生类不能删除继承的成员。
图8.1 基类规格说明
图8.2 基类和派生类
8.2 访问继承的成员(*)
8.3 所有类都派生自 object 类
object 类是唯一的非派生类,是继承层次结构的基础。
图8.3 类层次结构
8.4 屏蔽基类的成员
虽然派生类不能删除继承的成员,但可以声明相同名称的成员来进行屏蔽(覆盖)。屏蔽基类成员的要点如下:
- 声明一个相同类型、相同名称的成员。
- 如果屏蔽函数成员,则签名需要相同(签名不包括返回类型)。
- 使用 new 修饰符告诉编译器在故意屏蔽,否则编译器会发出警告。
- 也可以屏蔽静态成员。
图8.4 屏蔽基类成员
8.5 基类访问
基类访问表达式由关键字 base 后面跟着一个点和成员的名称组成:
图8.5 基类访问
下面的代码中,使用基类访问表达式访问了被隐藏的 Field1:
图8.5 访问被隐藏的 Field1
一般来说,能够有更优雅的设计避免这种情况,但是实在没办法的时候也可以使用这个特性。
8.6 使用基类的引用
使用类型转换运算符可以将派生类对象的引用转换为基类对象的引用,转换后的引用只能访问基类的成员。
- 基类的引用“看不到”派生类的其余部分,因为它通过基类类型的引用“看”这个对象。
图8.6 派生类的引用可以看到完整的 MyDerivedClass 对象,而 mybc 只能看到对象的 MyBaseClass 部分
8.6.1 虚方法和覆写方法
上述介绍的内容:使用基类引用访问派生类对象时,得到的是基类成员。
虚方法 可以使基类的引用访问“升至”派生类内。使用基类引用调用派生类的方法条件如下:
- 派生类方法和基类方法有相同的签名和返回类型。
- 基类的方法使用 virtual 标注。
- 派生类的方法使用 override 标注。
图8.7 虚方法和覆写方法
关于 virtual 和 override 的重要信息如下:
- 覆写和被覆写的方法必须有相同的可访问性。
- 不能覆写 static 方法或非虚方法。
- 方法、属性、索引器、事件都可以被声明为 virtual 和 override。
8.6.2 覆写标记为 override 的方法
覆写方法可以在继承的任何层次出现。使用基类部分的引用调用一个被覆写的方法时:
- 方法调用沿派生层次上溯执行,直到标记为 override 方法的最高派生版本。
- 如果有更高派生级别的方法但没有标记为 override,则不会被调用。
图8.8 具有派生层次的三个类
情况1:使用 override 声明 Print
图8.9 override 声明 Print 及输出结果
结果说明:无论是通过派生类还是通过基类引用调用,都会调用最高派生类中的方法。
图8.10 执行被传递到多层覆写链的顶端
情况2:使用 new 声明 Print
图8.11 new 声明 Print 及输出结果
结果说明:方法调用只向上传递了一级,因为最高层的方法没有被声明为 override。
图8.12 隐藏覆写的方法
8.6.3 覆盖其他类型成员(*)
8.7 构造函数的执行
- 要创建对象的基类部分,需要隐式调用基类的某个构造函数。
- 继承层次链中的每个类在执行自己的构造函数体之前执行它的基类构造函数。
class MyDerivedClass : MyBaseClass
{
MyDerivedClass() {
...
}
}
图8.13 对象构造的顺序
强烈反对在构造函数中调用虚方法。
8.7.1 构造函数初始化语句
- 第一种形式:使用关键字
base
指明使用哪一个基类构造函数。
图8.14 第一种形式初始化语句
当声明一个不带构造函数初始化语句的构造函数时,实际上是带有 base() 构造函数初始化语句的简写形式:
图8.15 等价的构造函数形式
- 第二种形式:使用关键字
this
指明使用当前类的哪一个构造函数。
图8.16 第二种形式初始化语句
当类需要多个构造函数时:
8.7.2 类访问修饰符
类的可访问性只有两个级别:
-
public:能够被系统内任何程序集中的代码访问。
-
internal:只能被自己所在程序集(不是程序,也不是 DLL)中的类看到。
默认访问级别是 internal。
图8.17 其他程序集中的类可以访问公有类但不能访问内部类
8.8 程序集间的继承
C# 允许在不同的程序集之间进行继承。要从其他程序集中的基类派生类,句许具备如下条件:
- 基类被声明为 public 以供外部访问。
- 添加对程序集的引用以找到基类。
- 增加对程序集的引用并不是使用 using 指令,而是告诉编译器所需的类型在哪里定义。
- using 指令是允许引用其他的类而不必使用它们的完全限定名称。
图8.18 Assembly1.cs
图8.19 Assembly2.cs
图8.20 跨程序集继承
8.9 成员访问修饰符
- 所有显示声明在类中的成员都是相互可见的,无论它们的访问性如何。即,同一类中的成员是相互透明的。
- 继承的成员不在类中显示声明,因此对派生类不一定可见。
- 成员的隐式访问级别为 private,一共有 5 中可访问级别:
- public
- private
- protected
- internal
- protected internal
- 成员的可访问性不能比类高。
8.9.1 访问成员的区域
如下所示,MyClass 被声明为 public,因此所有程序集都可以访问该类,但其成员是否可访问依赖于成员的访问修饰符:
图8.21 访问性的区域划分
另一个类能否访问 MyClass 的成员取决于该类的以下两个特征:
- 是否派生自 MyClass。
- 是否和 MyClass 在同一程序集。
依据这两个特征可以划分出图 8.21 中的 4 个区域。
8.9.2 公有成员的可访问性
public 访问级别的限制是最少的(几乎没有的),所有类都可以自由访问该成员。
图8.22 公有类的公有成员对同一程序集或其他程序集的所有类可见
8.9.3 私有成员的可访问性
private 访问级别的限制是最严格的,只有自己或嵌套在自己内部的类才能访问类成员,其他所有类都不行。
图8.23 任何类的私有成员只对它自己的类(或嵌套类)的成员可见
8.9.4 受保护成员的可访问性
比 private 限制稍宽松,在 private 的基础上还允许子类访问。
图8.24 公有类的受保护成员对它自己的类成员或派生类的成员可见,派生类甚至可以在其他程序集中
8.9.5 内部成员的可访问性
internal 允许成员对程序集内部的所有类可见,对程序集外部的所有类不可见。
图8.25 内部成员对同一程序集内部的任何类成员可见,但对程序集外部的类不可见
8.9.6 受保护内部成员的可访问性
protected internal 的访问性是 protected 和 internal 的 并集,不是交集。
图8.26 公有类的受保护内部成员对相同程序集的类成员或继承该类的类成员可见,对其他程序集中且不继承该类的类不可见
8.9.7 成员访问修饰符小结
表8.1 成员访问修饰符
图 8.27 演示了成员访问修饰符的可访问级别。
图8.27 各种成员访问修饰符的相对可访问性
表 8.2 总结了以上所有情况。
表8.2 成员可访问性总结
8.10 抽象成员
抽象成员是待被覆写的函数成员,具有如下特征:
- 必须是函数成员。
- 必须用 abstract 修饰符标记。
- 不能有实现代码块,而用分号代替。
图8.28 抽象成员的声明
以下类型的成员可以被声明为抽象成员:
其他重要事项如下:
- 不能将 virtual 修饰符附加到抽象成员上。
- 类似于虚成员,派生类中抽象成员的实现必须指定 override 修饰符。
表8.3 比较虚成员和抽象成员
8.11 抽象类
抽象类只能被用作其他类的基类。
- 不能创建抽象类的实例。
- 抽象类使用 abstract 修饰符声明。
- 抽象类可以包含抽象成员或非抽象成员。
- 抽象类自己可以派生自另一个抽象类。
- 任何派生自抽象类的类必须使用 override 关键字实现该类所有的抽象成员,除非自己也是抽象类。
图8.29 抽象类的声明
8.11.1 抽象类和抽象方法的示例(*)
8.11.2 抽象类的另一个例子(*)
8.12 密封类
- 密封类只能被用作独立的类,不能被用作基类。
- 使用 sealed 修饰符标注。
图8.30 密封类的声明
8.13 静态类
静态类的一个常见用途是创建一个包含一组数学方法和值的数学库。
- 类本身必须标记为 static。
- 所有成员必须是 static。
- 可以有一个静态构造函数,但不能有实例构造函数。
- 静态类隐式密封。
图8.31 静态类示例
C# 6.0后,可以使用 using static 指令访问静态类的成员,而不必使用类名。
8.14 扩展方法
几乎整个 LINQ 库都是通过扩展方法来实现的。
- 声明扩展方法的类必须声明为 static。
- 扩展方法本身必须声明为 static。
- 扩展方法必须包含关键字 this 作为第一个参数类型,后面跟着拓展类的名称。
图8.32 扩展方法的声明和结构
8.15 命名约定
表8.4 常用的标识符命名风格
说明:下划线不常出现在标识符的中间位置。