深入剖析C#构造函数执行:基类调用、初始化顺序与访问控制

导言

在面向对象编程中,理解对象构造过程至关重要。C#的构造函数执行遵循严格的顺序规则,尤其是涉及继承和成员初始化时。本文将深入解析构造函数的执行流程、初始化语句的妙用以及类访问修饰符的影响,助你写出更健壮、可维护的代码。

构造函数的执行顺序(核心规则)

对象创建的起点:成员初始化

对象创建时,最先执行的是所有实例字段的初始化(无论是否显式赋值)。未赋值的字段获得其类型的默认值(如 int 默认为 0)。

csharp 复制代码
class MyDerivedClass : MyBaseClass 
{
    int MyField1 = 5; // 1. 初始化 (值=5)
    int MyField2;     // 1. 初始化 (值=0, 默认值)
    ...
}

基类优先:向上追溯

完成实例成员初始化后,立即隐式调用基类的构造函数。若未显式指定基类构造函数,则调用基类的无参构造函数 base()。

csharp 复制代码
public MyDerivedClass() // 2. 隐式调用基类构造函数 MyBaseClass()
{
    ... // 3. 最后执行派生类构造函数体
}

自身构造:最后执行

基类构造函数执行完毕后,最后才执行派生类自身的构造函数体中的代码。

📌 总结顺序:

派生类成员初始化 → 基类构造函数调用 → 派生类构造函数体执行

⚠️ 重要警告:构造函数中避免调用虚方法!

  • 原因: 当基类构造函数调用虚方法时,如果该方法在派生类中被重写,会提前跳转到派生类的重写方法。
  • 风险: 此时派生类的构造函数体尚未执行,其字段可能未完成初始化(仅完成默认初始化),处于"未完全就绪"状态,极易引发 NullReferenceException 或逻辑错误。
  • 建议: 在构造函数中严格避免调用可被派生类重写的虚方法、抽象方法或接口方法。

掌控构造:构造函数初始化语句

C# 提供了强大的语法让你精确控制调用哪个构造函数。

base 关键字:指定基类构造函数

当基类有多个构造函数时,使用 base 显式选择调用哪一个。

csharp 复制代码
class MyDerivedClass : MyBaseClass
{
    public MyDerivedClass(int x, string s) : base(x, s) // 显式调用基类带参构造 
    {
        ... // 基类构造执行完后,执行此派生类构造体 
    }
}

this 关键字:复用同类构造函数

用于在当前类的不同构造函数之间复用初始化逻辑,避免代码重复。

csharp 复制代码
class MyClass
{
    public MyClass(string firstName) : this() // 先调用本类的无参构造 
    {
        UserName = firstName;
        UserId = -1;
    }

    public MyClass(int id) : this() // 也先调用本类的无参构造
    {
        UserName = "Anonymous";
        UserId = id;
    }

    private MyClass() // 公共初始化逻辑放在私有构造中
    {
        // 初始化 readonly 字段或公共设置 
        CommonInit1 = 10;
        CommonInit2 = 20;
    }
}

默认行为:base() 的简写

如果声明构造函数时未指定初始化语句(: base(...) 或 : this(...)),编译器自动添加 : base(),表示调用基类无参构造函数。

类的可见性:public vs internal

类的访问级别决定了它在程序集内外的可见性。

public:全局可见

  • 声明:public class MyPublicClass { ... }
  • 含义:该类可以被任何程序集(.exe 或 .dll)中的代码访问。是跨程序集协作的标准方式。

internal:程序集内可见 (默认)

  • 声明:internal class MyInternalClass { ... } 或 class MyInternalClass { ... } (省略修饰符时默认 internal)
  • 含义:该类只能在定义它的程序集内部被访问。程序集外部的代码无法看到或使用它。常用于封装实现细节。

🔍 访问规则图解

  • 程序集A:包含 internal class MyClass 和 public class OtherClass。

  • 程序集B:尝试访问程序集A中的类。

    • ❌ MyClass:标记为 internal,不可访问(仅程序集A内部可见)。
    • ✅ OtherClass:标记为 public,可访问(对任何引用程序集A的程序集可见)。

    关键实践建议

  • 明确初始化顺序 : 牢记"成员初始化 → 基类构造 → 自身构造体"的铁律,避免依赖未正确初始化的字段。
    善用 base 和 this:

    • 使用 base 确保基类以正确状态初始化。
    • 使用 this 提炼公共构造逻辑,减少重复代码,尤其适用于复杂初始化或 readonly 字段赋值(readonly 字段只能在声明时或构造函数中初始化)。
  • 优先 internal: 除非明确需要被其他程序集使用,否则优先将类声明为 internal,遵循"最小公开原则",提升封装性和安全性。

  • 构造函数"纯洁性": 避免在构造函数中调用虚方法、启动异步操作或执行复杂的、可能失败的外部依赖调用。将构造职责限定为初始化对象状态。

透彻理解构造函数和可见性,是构建健壮、可扩展C#应用的基石! 你在项目中遇到过哪些构造函数相关的"坑"?欢迎留言分享讨论!

相关推荐
要开心吖ZSH18 分钟前
《Spring 中上下文传递的那些事儿》Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
java·分布式·spring
桦说编程37 分钟前
深入解析CompletableFuture源码实现
java·性能优化·源码
傻啦嘿哟39 分钟前
Python 办公实战:用 python-docx 自动生成 Word 文档
开发语言·c#
蓝澈11211 小时前
迪杰斯特拉算法之解决单源最短路径问题
java·数据结构
Kali_071 小时前
使用 Mathematical_Expression 从零开始实现数学题目的作答小游戏【可复制代码】
java·人工智能·免费
rzl022 小时前
java web5(黑马)
java·开发语言·前端
时序数据说2 小时前
为什么时序数据库IoTDB选择Java作为开发语言
java·大数据·开发语言·数据库·物联网·时序数据库·iotdb
戒不掉的伤怀2 小时前
【Navicat 连接MySQL时出现错误1251:客户端不支持服务器请求的身份验证协议;请考虑升级MySQL客户端】
服务器·数据库·mysql
cv高级工程师YKY2 小时前
服务器 - - QPS与TPS介绍
数据库
guojl2 小时前
深度解读jdk8 HashMap设计与源码
java