C# 核心进阶:深度解析继承(Inheritance)与多态机制

C# 核心进阶:深度解析继承(Inheritance)与多态机制

在面向对象编程(OOP)中,继承是代码复用和构建类层次结构的核心。通过继承,子类可以扩展或定制基类的功能,而无须从零开始构建。


1. 多态(Polymorphism)与引用转换

多态是继承的灵魂。在 C# 中,引用是多态的,这意味着父类型的变量可以指向其子类的对象。

引用转换

  • 向上转换(Upcasting):从子类引用创建基类引用。这是隐式的,且仅影响引用类型,不影响被引用的对象本身。
  • 向下转换(Downcasting):从基类引用创建子类引用。这必须是显式的,因为它在运行时可能失败并抛出异常。

类型转换工具

为了安全地进行向下转换,C# 提供了以下运算符:

  • as 运算符:转换失败时返回 null 而非抛出异常。
  • is 运算符:检查对象是否满足特定模式(如是否属于某个特定类),常用于转换前的类型检查。
  • 模式变量:C# 支持在 is 检查的同时引入变量(如 if (obj is Subclass s)),引入的变量可立即使用。

2. 虚函数成员与重写

子类可以通过重写基类的成员来改变其行为。

  • virtual 关键字:允许在子类中被重写。方法、属性、索引器和事件均可声明为虚成员。
  • override 关键字:用于提供虚成员的特定实现。重写时,方法的签名、返回值和访问权限必须保持一致。
  • 协变返回类型(C# 9):允许重写方法时返回比基类定义更具体的派生类型。

⚠️ 安全警告:从构造器调用虚方法具有潜在危险。因为子类重写的方法可能会在子类字段尚未完全初始化之前被调用。


3. 抽象类与密封类

  • 抽象类 (abstract):不能被实例化,仅作为基类使用。它可以包含不提供默认实现的抽象成员,强制子类实现这些成员。
  • 密封类 (sealed):防止类被继承。同样,sealed 也可以作用于重写的函数成员,防止其在更深层的子类中被再次重写。

4. 隐藏继承成员(Shadowing)

当子类定义了与基类同名的成员时,会发生成员隐藏

  • 编译器默认会发出警告。
  • 使用 new 修饰符 可以明确告诉编译器这种隐藏是有意为之,从而消除警告。
  • 注意:new 修饰符隐藏成员与 override 重写成员在多态下的表现完全不同。

5. base 关键字的妙用

base 关键字在子类中有两个核心用途:

  1. 访问基类成员:调用被子类重写或隐藏的基类函数实现。
  2. 调用基类构造器:在子类构造器中显式指定调用父类的哪个构造方法。

6. 构造器的执行与初始化顺序

继承体系下的对象实例化有着严格的执行顺序,理解这一点对排查 Bug 至关重要:

总体原则

  1. 基类构造器优先:基类的初始化总是先于子类的特定初始化执行。
  2. 隐式调用:如果子类构造器未显式使用 base 关键字,编译器会自动尝试调用基类的无参数构造器。

详细步骤

阶段 执行动作 顺序
初始化阶段 字段初始化 & 计算基类构造器参数 从子类到基类
执行阶段 构造器方法体执行 从基类到子类

7. 方法重载与解析

当重载方法在继承体系中被调用时,编译器会根据以下规则决定执行哪个版本:

  • 优先匹配最明确的类型:选择参数类型最贴近传入实参的重载版本。
  • 静态决定:具体调用哪个重载是在编译时静态决定的,而不是在运行时动态决定的。

技术总结
继承不仅是“获取父类的代码”,更是一种“是一个(is-a)”的逻辑关系。
通过合理使用 virtualabstractsealed,可以构建出既灵活又安全的类层次结构。在处理构造器时,务必注意字段初始化与方法体执行的先后逻辑。

定义Book

定义一个表示“书”的类,涵盖自动属性、只读字段、构造器和表达式体方法。

using System;

namespace LibrarySystem
{
    
    public class Book
    {
        // 字段 (Field)
        private readonly string _id;

        // 属性 (Property)
        // 使用自动属性和 init-only setter(C# 9),初始化后不可修改
        public string Title { get; init; }
        public string Author { get; set; }
        public decimal Price { get; set; }

        //  构造器 (Constructor)
        // 执行类初始化代码,名称与类型相同
        public Book(string id, string title, string author)
        {
            _id = id;
            Title = title;
            Author = author;
        }

        // 方法 (Method)
        // 使用表达式体方法简写形式
        public virtual void DisplayInfo() => 
            Console.WriteLine($"ID: {_id}, 书名: {Title}, 作者: {Author}, 价格: {Price:C}");

        // 解构器 (Deconstructor)
        // 将对象属性反向赋值给变量
        public void Deconstruct(out string title, out string author)
        {
            title = Title;
            author = Author;
        }
    }
}


通过在之前定义的 Book 类基础上创建一个名为 EBook 的派生类(子类),来直观地解释继承的核心概念

在 C# 中,使用 : 符号表示继承。子类会自动获得父类的所有非私有成员。

using System;

namespace LibrarySystem
{

    public class EBook : Book
    {
        // 子类特有的属性
        public string DownloadUrl { get; set; }
        public double FileSizeMB { get; set; }

        // 子类构造器
        // 必须使用 base 关键字调用父类构造器,因为父类没有无参构造器
        public EBook(string id, string title, string author, string downloadUrl) 
            : base(id, title, author) 
        {
            DownloadUrl = downloadUrl;
        }

        // 重写父类方法 (Polymorphism)
        // 注意:父类的 DisplayInfo 需标记为 virtual 才能被 override
        public override void DisplayInfo()
        {
            // 使用 base 调用父类的逻辑,避免重复代码
            base.DisplayInfo(); 
            Console.WriteLine($"[电子书特有] 下载地址: {DownloadUrl}, 文件大小: {FileSizeMB}MB");
        }
    }
}

测试父类与子类直接转换

class Program
{
    static void Main()
    {
        // 实例化子类
        EBook myEBook = new EBook("EB001", "C# 核心技术", "李四", "https://example.com/dl");
        myEBook.Price = 29.9m;
        myEBook.FileSizeMB = 15.5;

        // 多态:父类型变量指向子类对象
        //C# 中被称为“向上转换”(Upcasting),是隐式且安全的
        Book normalBook = myEBook; 

        // 调用的是 EBook 重写后的 DisplayInfo
        normalBook.DisplayInfo(); 
        
        //访问限制
        // normalBook.DownloadUrl = "..."; // 编译错误!
        // 解释:虽然对象确实有这个属性,但因为变量类型是 Book
        // 编译器在编译阶段只能看到 Book 定义的成员
    }
}


理解继承的关键点

继承的四个核心技术:

  1. 代码重用 (Reuse)EBook 不需要重新定义 TitleAuthorPrice,它直接从 Book 类“继承”了这些功能。
  2. 构造器链 (Constructor Chaining):创建 EBook 时,系统会先调用 Book 的构造器完成基础初始化,再执行 EBook 自己的逻辑。这是通过 base 关键字实现的。
  3. 方法重写 (Overriding)EBook 通过 override 改变了 DisplayInfo 的行为,使其能打印出下载链接。这体现了多态:同样的方法名,在不同对象上有不同的表现。
  4. 向上转换 (Upcasting):我们可以把 EBook 对象赋值给 Book 类型的变量(如 Book normalBook = myEBook)。这是隐式且安全的,因为“电子书也是一种书”。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容