构造函数需无返回类型、名同类名、不可显式调用;推荐this链式调用、字段非空校验、避免虚方法调用;析构函数非资源清理首选,仅作非托管资源兜底;Dispose模式须配合GC.SuppressFinalize实现两阶段清理。
构造函数不是普通方法,它没有返回类型(连 void 都不能写),名字必须和类名完全一致,且不能被显式调用。常见错误是加了 void 或拼错类名,编译器会直接报错:CS0501: 'X.X()' must declare a body because it is not marked abstract, extern, or partial。
建议做法:
this(...) 链式调用,避免重复初始化逻辑ArgumentNullException
virtual 或 abstract),子类可能尚未完成初始化,容易引发未定义行为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() 时走快速路径;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.Collect(),析构函数也不会立刻执行,而是被放入终结队列,由专用线程异步调用this 存进静态集合),它会被提升到更高代,析构延迟更久甚至永不执行所以别靠打印日志验证析构函数是否工作,更别在里面写业务逻辑。它的存
在意义只有一个:防止非托管资源泄漏的最后防线,仅此而已。