C# 图解教程 第5版 —— 第8章 类和继承

8.1 类继承

  • 要派生一个类,需要在类名后面加入 基类规格说明,由冒号和基类名称组成。
  • 派生类不能删除继承的成员。
image-20231026111011929
图8.1 基类规格说明
image-20231026111117403
图8.2 基类和派生类

8.2 访问继承的成员(*)

8.3 所有类都派生自 object 类

​ object 类是唯一的非派生类,是继承层次结构的基础。

image-20231026111341681
图8.3 类层次结构

8.4 屏蔽基类的成员

​ 虽然派生类不能删除继承的成员,但可以声明相同名称的成员来进行屏蔽(覆盖)。屏蔽基类成员的要点如下:

  • 声明一个相同类型、相同名称的成员。
    • 如果屏蔽函数成员,则签名需要相同(签名不包括返回类型)。
  • 使用 new 修饰符告诉编译器在故意屏蔽,否则编译器会发出警告。
  • 也可以屏蔽静态成员。
image-20231026112022389 image-20231026112036517
图8.4 屏蔽基类成员

8.5 基类访问

​ 基类访问表达式由关键字 base 后面跟着一个点和成员的名称组成:

image-20231026112139703
图8.5 基类访问

​ 下面的代码中,使用基类访问表达式访问了被隐藏的 Field1:

image-20231026112253307
图8.5 访问被隐藏的 Field1

​ 一般来说,能够有更优雅的设计避免这种情况,但是实在没办法的时候也可以使用这个特性。

8.6 使用基类的引用

​ 使用类型转换运算符可以将派生类对象的引用转换为基类对象的引用,转换后的引用只能访问基类的成员。

  • 基类的引用“看不到”派生类的其余部分,因为它通过基类类型的引用“看”这个对象。
image-20231026112751219 image-20231026112759875
图8.6 派生类的引用可以看到完整的 MyDerivedClass 对象,而 mybc 只能看到对象的 MyBaseClass 部分

8.6.1 虚方法和覆写方法

​ 上述介绍的内容:使用基类引用访问派生类对象时,得到的是基类成员。

虚方法 可以使基类的引用访问“升至”派生类内。使用基类引用调用派生类的方法条件如下:

  • 派生类方法和基类方法有相同的签名和返回类型。
  • 基类的方法使用 virtual 标注。
  • 派生类的方法使用 override 标注。
image-20231026113203563 image-20231026113217159 image-20231026113259442
图8.7 虚方法和覆写方法

​ 关于 virtual 和 override 的重要信息如下:

  • 覆写和被覆写的方法必须有相同的可访问性。
  • 不能覆写 static 方法或非虚方法。
  • 方法、属性、索引器、事件都可以被声明为 virtual 和 override。

8.6.2 覆写标记为 override 的方法

​ 覆写方法可以在继承的任何层次出现。使用基类部分的引用调用一个被覆写的方法时:

  1. 方法调用沿派生层次上溯执行,直到标记为 override 方法的最高派生版本。
  2. 如果有更高派生级别的方法但没有标记为 override,则不会被调用。
image-20231026113814694
图8.8 具有派生层次的三个类

情况1:使用 override 声明 Print

image-20231026113958997 image-20231026114009930
图8.9 override 声明 Print 及输出结果

​ 结果说明:无论是通过派生类还是通过基类引用调用,都会调用最高派生类中的方法。

image-20231026114037205
图8.10 执行被传递到多层覆写链的顶端

情况2:使用 new 声明 Print

image-20231026114350210 image-20231026114414493
图8.11 new 声明 Print 及输出结果

​ 结果说明:方法调用只向上传递了一级,因为最高层的方法没有被声明为 override。

image-20231026114426173
图8.12 隐藏覆写的方法

8.6.3 覆盖其他类型成员(*)

8.7 构造函数的执行

  • 要创建对象的基类部分,需要隐式调用基类的某个构造函数。
  • 继承层次链中的每个类在执行自己的构造函数体之前执行它的基类构造函数。
class MyDerivedClass : MyBaseClass
{
    MyDerivedClass() { // 构造函数调用基类构造函数 MyBaseClass()
        ...
    }
}
image-20231026114936221
图8.13 对象构造的顺序

​ 强烈反对在构造函数中调用虚方法。

8.7.1 构造函数初始化语句

  • 第一种形式:使用关键字 base 指明使用哪一个基类构造函数。
image-20231026115315583
图8.14 第一种形式初始化语句

​ 当声明一个不带构造函数初始化语句的构造函数时,实际上是带有 base() 构造函数初始化语句的简写形式:

image-20231026115451437
图8.15 等价的构造函数形式
  • 第二种形式:使用关键字 this 指明使用当前类的哪一个构造函数。
image-20231026115557173
图8.16 第二种形式初始化语句

​ 当类需要多个构造函数时:

  • 推荐做法:提取公共代码至一个构造函数内,其他构造函数使用该公共构造函数进行初始化。

  • 不推荐做法:提取公共代码至一个方法内,让所有构造函数调用这个方法。

    原因:

    • 编译器对构造函数能有一些特殊优化。
    • 有些事情必须在构造函数中进行,例如初始化 readonly 字段。

8.7.2 类访问修饰符

​ 类的可访问性只有两个级别:

  1. public:能够被系统内任何程序集中的代码访问。

  2. internal:只能被自己所在程序集(不是程序,也不是 DLL)中的类看到。

​ 默认访问级别是 internal。

image-20231026120324534
图8.17 其他程序集中的类可以访问公有类但不能访问内部类

8.8 程序集间的继承

​ C# 允许在不同的程序集之间进行继承。要从其他程序集中的基类派生类,句许具备如下条件:

  1. 基类被声明为 public 以供外部访问。
  2. 添加对程序集的引用以找到基类。
    • 增加对程序集的引用并不是使用 using 指令,而是告诉编译器所需的类型在哪里定义。
    • using 指令是允许引用其他的类而不必使用它们的完全限定名称。
image-20231026145046102
图8.18 Assembly1.cs
image-20231026145220559 image-20231026145157532
图8.19 Assembly2.cs
image-20231026145327305
图8.20 跨程序集继承

8.9 成员访问修饰符

  • 所有显示声明在类中的成员都是相互可见的,无论它们的访问性如何。即,同一类中的成员是相互透明的。
  • 继承的成员不在类中显示声明,因此对派生类不一定可见。
  • 成员的隐式访问级别为 private,一共有 5 中可访问级别:
    • public
    • private
    • protected
    • internal
    • protected internal
  • 成员的可访问性不能比类高。

8.9.1 访问成员的区域

​ 如下所示,MyClass 被声明为 public,因此所有程序集都可以访问该类,但其成员是否可访问依赖于成员的访问修饰符:

image-20231026150324683 image-20231026150313814
图8.21 访问性的区域划分

​ 另一个类能否访问 MyClass 的成员取决于该类的以下两个特征:

  1. 是否派生自 MyClass。
  2. 是否和 MyClass 在同一程序集。

​ 依据这两个特征可以划分出图 8.21 中的 4 个区域。

8.9.2 公有成员的可访问性

​ public 访问级别的限制是最少的(几乎没有的),所有类都可以自由访问该成员。

image-20231026150654828
图8.22 公有类的公有成员对同一程序集或其他程序集的所有类可见

8.9.3 私有成员的可访问性

​ private 访问级别的限制是最严格的,只有自己或嵌套在自己内部的类才能访问类成员,其他所有类都不行。

image-20231026150822749
图8.23 任何类的私有成员只对它自己的类(或嵌套类)的成员可见

8.9.4 受保护成员的可访问性

​ 比 private 限制稍宽松,在 private 的基础上还允许子类访问。

image-20231026151032794
图8.24 公有类的受保护成员对它自己的类成员或派生类的成员可见,派生类甚至可以在其他程序集中

8.9.5 内部成员的可访问性

​ internal 允许成员对程序集内部的所有类可见,对程序集外部的所有类不可见。

image-20231026151208350
图8.25 内部成员对同一程序集内部的任何类成员可见,但对程序集外部的类不可见

8.9.6 受保护内部成员的可访问性

​ protected internal 的访问性是 protected 和 internal 的 并集,不是交集。

image-20231026151405084
图8.26 公有类的受保护内部成员对相同程序集的类成员或继承该类的类成员可见,对其他程序集中且不继承该类的类不可见

8.9.7 成员访问修饰符小结

表8.1 成员访问修饰符
image-20231026151528212

​ 图 8.27 演示了成员访问修饰符的可访问级别。

image-20231026151630946
图8.27 各种成员访问修饰符的相对可访问性

​ 表 8.2 总结了以上所有情况。

表8.2 成员可访问性总结
image-20231026151738219

8.10 抽象成员

​ 抽象成员是待被覆写的函数成员,具有如下特征:

  • 必须是函数成员。
  • 必须用 abstract 修饰符标记。
  • 不能有实现代码块,而用分号代替。
image-20231026190311331
图8.28 抽象成员的声明

​ 以下类型的成员可以被声明为抽象成员:

  • 方法。
  • 属性。
  • 事件。
  • 索引器。

​ 其他重要事项如下:

  1. 不能将 virtual 修饰符附加到抽象成员上。
  2. 类似于虚成员,派生类中抽象成员的实现必须指定 override 修饰符。
表8.3 比较虚成员和抽象成员
image-20231026190544687

8.11 抽象类

​ 抽象类只能被用作其他类的基类。

  • 不能创建抽象类的实例。
  • 抽象类使用 abstract 修饰符声明。
  • 抽象类可以包含抽象成员或非抽象成员。
  • 抽象类自己可以派生自另一个抽象类。
  • 任何派生自抽象类的类必须使用 override 关键字实现该类所有的抽象成员,除非自己也是抽象类。
image-20231026190825211
图8.29 抽象类的声明

8.11.1 抽象类和抽象方法的示例(*)

8.11.2 抽象类的另一个例子(*)

8.12 密封类

  • 密封类只能被用作独立的类,不能被用作基类。
  • 使用 sealed 修饰符标注。
image-20231026191009984
图8.30 密封类的声明

8.13 静态类

​ 静态类的一个常见用途是创建一个包含一组数学方法和值的数学库。

  • 类本身必须标记为 static。
  • 所有成员必须是 static。
  • 可以有一个静态构造函数,但不能有实例构造函数。
  • 静态类隐式密封。
image-20231026191154082 image-20231026191206089
图8.31 静态类示例

​ C# 6.0后,可以使用 using static 指令访问静态类的成员,而不必使用类名。

8.14 扩展方法

​ 几乎整个 LINQ 库都是通过扩展方法来实现的。

  • 声明扩展方法的类必须声明为 static。
  • 扩展方法本身必须声明为 static。
  • 扩展方法必须包含关键字 this 作为第一个参数类型,后面跟着拓展类的名称。
image-20231026191619861 image-20231026191633334
图8.32 扩展方法的声明和结构

8.15 命名约定

表8.4 常用的标识符命名风格
image-20231026192028687

​ 说明:下划线不常出现在标识符的中间位置。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔗理苦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值