由于.NET拥有垃圾收集器,为什么我们还需要finalizers/destructors/dispose-pattern呢?

40 浏览
0 Comments

由于.NET拥有垃圾收集器,为什么我们还需要finalizers/destructors/dispose-pattern呢?

如果我理解正确,.net运行时将始终为我进行垃圾回收。因此,如果我创建新的对象并且在代码中停止引用它们,运行时将清理这些对象并释放它们占用的内存。

既然如此,为什么一些对象需要有析构函数或dispose方法?当它们不再被引用时,运行时不会为它们进行清理吗?

admin 更改状态以发布 2023年5月22日
0
0 Comments

前面的回答都很好,但是让我再次强调一下重要点。特别是,您说

如果我理解正确,.net运行时将始终为我清理。

这只是部分正确。实际上,.NET仅对一种特定资源进行自动管理:主存储器。所有其他资源都需要手动清理。

奇怪的是,主存储器在几乎所有关于程序资源的讨论中都具有特殊地位。当然,这有一个很好的理由-主内存通常是最稀缺的资源之一。但是值得记住,还有其他类型的资源,也需要管理。


1)通常尝试的解决方案是将其他资源的生命周期与代码中的内存位置或标识符的生命周期耦合-因此存在终结器。

0
0 Comments

Finalizers(终结器)的作用是确保将稀缺资源(如文件句柄、套接字、核心对象等)释放回系统。由于终结器总是在对象生命结束时运行,因此它是释放这些句柄的指定位置。

使用Dispose(清理)模式来提供资源的确定性销毁。由于.NET运行时垃圾收集器是不确定的(这意味着您永远无法确定运行时何时收集旧对象并调用其终结器),因此需要一种方法来确保确定性地释放系统资源。因此,当您正确实现Dispose模式时,您会提供资源的确定性释放,并在使用者不注意并且不释放对象的情况下,终结器会清理对象。

一个需要使用Dispose的简单例子可能是快速而简单的日志方法:

public void Log(string line)
{
    var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None));
    sw.WriteLine(line);
    // Since we don't close the stream the FileStream finalizer will do that for 
    // us but we don't know when that will be and until then the file is locked.
}

在上面的例子中,文件将一直被锁定,直到垃圾收集器调用StreamWriter对象的终结器。这会产生一个问题,因为与此同时,方法可能会再次被调用以写入日志,但是这次将失败,因为文件仍被锁定。

正确的方法是在使用结束时释放对象:

public void Log(string line)
{
    using (var sw = new StreamWriter(File.Open(
        "LogFile.log", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))) {
        sw.WriteLine(line);
    }
    // Since we use the using block (which conveniently calls Dispose() for us)
    // the file well be closed at this point.
}

顺便说一下,严格来说,finalizers和destructors意思相同;我喜欢称呼C#析构函数为“finalizers”,因为否则它们往往会与C++的析构函数混淆,后者与C#不同,具有确定性。

0