c# 构造函数和析构函数

7次阅读

构造函数需无返回类型、名同类名、不可显式调用;推荐 this 链式调用、字段非空校验、避免虚方法调用;析构函数非资源清理首选,仅作非托管资源兜底;Dispose 模式须配合 GC.SuppressFinalize 实现两阶段清理。

c# 构造函数和析构函数

构造函数怎么写才不会出错

构造函数不是普通方法,它没有返回类型(连 void 都不能写),名字必须和类名完全一致,且不能被显式调用。常见错误是加了 void 或拼错类名,编译器会直接报错:CS0501: 'X.X()' must declare a body because it is not marked abstract, extern, or partial

建议做法:

  • 多个构造函数之间用 this(……) 链式调用,避免重复初始化逻辑
  • 如果类有字段需要非空校验,优先在构造函数里检查并抛出 ArgumentNullException
  • 不要在构造函数里调用虚方法(virtualabstract),子类可能尚未完成初始化,容易引发未定义行为
public class Person {public string Name { get;}     public int Age {get;} 
public Person(string name) : this(name, 0) {}  public Person(string name, int age) {Name = name ?? throw new ArgumentNullException(nameof(name));     Age = Math.Max(0, age); }

}

析构函数不是“资源清理首选”

C# 的析构函数(即 ~ClassName())本质是编译器生成的 Finalize() 重写,它由 GC 在不确定时间调用,** 不能保证执行时机,也不能保证一定执行 **。它不适用于释放文件句柄、数据库连接、网络流等需要及时释放的资源。

真正该用的是 IDisposable 接口 + using 语句。析构函数只应在极少数场景下作为“安全网”:当用户忘记调用 Dispose(),且你持有非托管资源(如通过 Marshal.AllocHGlobal 分配的内存)时,才在析构函数里做兜底释放。

注意点:

  • 析构函数不能带访问修饰符(不能写 public ~MyClass()
  • 一个类最多只能有一个析构函数,且不能被继承或重载
  • 如果实现了 IDisposable,应在 Dispose(bool) 中调用 GC.SuppressFinalize(this),防止析构函数被重复执行

Dispose 模式怎么配合析构函数写

标准 Dispose 模式不是可选项,而是处理混合资源(托管 + 非托管)的强制约定。核心是两阶段清理:用户调用 Dispose() 时走快速路径;GC 调用析构函数时走慢速兜底路径。

关键结构:

  • Dispose() 方法只负责标记已释放,并调用 Dispose(true)
  • Dispose(bool disposing) 中,disposing == true 表示可安全访问托管对象;false 表示只能操作非托管资源
  • 析构函数里只调用 Dispose(false),绝不调用任何托管对象的方法或属性
public class FileReader : IDisposable {private bool _disposed = false;     private IntPtr _fileHandle = IntPtr.Zero; 
~FileReader() {     Dispose(false); }  public void Dispose() {     Dispose(true);     GC.SuppressFinalize(this); }  protected virtual void Dispose(bool disposing) {if (_disposed) return;      if (disposing)     {// 可以安全释放托管资源,比如关闭 StreamReader}      // 总是释放非托管资源     if (_fileHandle != IntPtr.Zero)     {Marshal.FreeHGlobal(_fileHandle);         _fileHandle = IntPtr.Zero;     }      _disposed = true; }

}

为什么 new 之后没调用析构函数

这是最常被误解的一点:析构函数不是“对象销毁时自动触发”,而是“GC 决定回收该对象内存前,可能调用 Finalizer 队列里的方法”。这意味着:

  • 程序运行中几乎看不到析构函数执行——GC 可能一直不触发,尤其内存充足时
  • 即使调用了 GC.Collect(),析构函数也不会立刻执行,而是被放入终结队列,由专用线程异步调用
  • 如果对象在 Finalizer 运行前又被其他地方引用(例如在析构函数里把 this 存进静态集合),它会被提升到更高代,析构延迟更久甚至永不执行

所以别靠打印日志验证析构函数是否工作,更别在里面写业务逻辑。它的存在意义只有一个:防止非托管资源泄漏的最后防线,仅此而已。

星耀云
版权声明:本站原创文章,由 星耀云 2026-01-06发表,共计1958字。
转载说明:转载本网站任何内容,请按照转载方式正确书写本站原文地址。本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。
text=ZqhQzanResources