c# 构造函数和析构函数

来源:这里教程网 时间:2026-02-21 17:36:55 作者:

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

构造函数不是普通方法,它没有返回类型(连

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; }
<pre class='brush:php;toolbar:false;'>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;
<pre class='brush:php;toolbar:false;'>~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
存进静态集合),它会被提升到更高代,析构延迟更久甚至永不执行

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

相关推荐