完成vs Dispose

tus*_*h1r 207 c# dispose

为什么有些人在Finalize方法上使用该Dispose方法?

在什么情况下你会使用Finalize方法而不是Dispose方法,反之亦然?

Sam*_*uel 131

当您的对象被垃圾收集时调用终结器方法,并且您无法保证何时会发生这种情况(您可以强制它,但这会损害性能).

Dispose另一方面,该方法旨在由创建您的类的代码调用,以便您可以在代码完成时清理和释放您已获取的任何资源(非托管数据,数据库连接,文件句柄等).你的对象.

标准做法是实施IDisposable,Dispose以便您可以在using陈述中使用您的对象.如using(var foo = new MyObject()) { }.在你的终结者中,你打电话Dispose,以防呼叫代码忘记丢弃你.

  • 从Finalize实现中调用Dispose需要小心一点 - Dispose也可以处理您不希望从终结器触摸的托管资源,因为它们可能已经自己完成了. (16认同)
  • @itowlson:检查null并假设对象可以被处理两次(第二次调用什么都不做)应该足够好了. (6认同)
  • 标准的IDisposal模式和处理托管组件可选的Dispose(bool)的隐藏实现似乎迎合了这个问题. (6认同)

Bri*_*sen 115

其他人已经介绍了Dispose和之间的区别Finalize(顺便说一下,这个Finalize方法在语言规范中仍然被称为析构函数),所以我只想添加一些关于Finalize方法派上用场的方案.

某些类型以易于使用的方式封装一次性资源,并在一次操作中处理它们.一般用法通常是这样的:打开,读取或写入,关闭(Dispose).它非常适合using构造.

其他人则有点困难.WaitEventHandles因为它们用于从一个线程发送到另一个线程,所以实例不会像这样使用.那么问题就变成谁应该呼吁Dispose这些?作为这样的安全措施类型实现了一种Finalize方法,该方法确保在应用程序不再引用实例时处理资源.

  • 我无法理解这个批准的答案.我还是想知道不同的.这是什么? (57认同)
  • @Ismael:最终的情况是`Finalize`可能是合理的,当有许多对象对资源保持活跃感兴趣时,但是没有办法让一个对资源不再感兴趣的对象能够找到如果它是最后一个.在这种情况下,`Finalize`通常只会在没有人对该对象感兴趣时触发.对于诸如文件和锁之类的非可替换资源,"Finalize"的松散时间是可怕的,但对于可替换资源可能是好的. (22认同)
  • 给一个伟大的新(对我而言)单词+1 to supercat.上下文非常清楚,但是对于我们其他人来说,这就是维基百科所说的:"可变性是一种商品或商品的财产,其单个单位能够相互替代,例如甜原油,股份公司,债券,贵金属或货币." (13认同)
  • @JonCoombs:这是非常正确的,尽管可能值得注意的是,"可替代资源"一词适用于可自由替代的东西*直到它们被获得*并在释放或放弃后再次自由替代*.如果系统有一个锁对象池并且代码获取了一个与某个实体关联的对象,那么只要有人持有该锁*的引用以便将其与该实体*相关联,那么该锁可能不是被任何其他人取代.但是,如果所有关心被保护实体的代码都放弃了锁定,那么...... (5认同)

ito*_*son 64

Finalize是一个backstop方法,当它回收一个对象时由垃圾收集器调用.Dispose是"确定性清理"方法,由应用程序调用以在不再需要时释放有价值的本机资源(窗口句柄,数据库连接等),而不是将它们无限期地保留,直到GC转向对象.

作为对象的用户,您始终使用Dispose.最终确定是GC.

作为类的实现者,如果您拥有应该处理的托管资源,则实现Dispose.如果您拥有本机资源,则实现Dispose和Finalize,并且都调用释放本机资源的公共方法.这些习语通常通过私有Dispose(bool disposing)方法组合,Dispose调用为true,Finalize调用为false.此方法始终释放本机资源,然后检查disposing参数,如果为true,则会释放托管资源并调用GC.SuppressFinalize.

  • 对于混合了自清洁("管理")和非自清洁("非管理")资源的类的原始推荐模式早已过时.一个更好的模式是将每个非托管资源分别包装到它自己的托管对象中,该托管对象不包含任何对其清理不必要的任何强引用.可终结对象持有直接或间接强引用的所有内容都将延长其GC寿命.封装清理所需的东西可以避免延长GC生命周期. (2认同)
  • @JCoombs:`Dispose` 很好,正确实施它通常很容易。`Finalize` 是邪恶的,正确实施它通常很困难。除此之外,因为 GC 将确保只要存在对该对象的任何引用,就不会“回收”任何对象的身份,所以很容易清理一堆“一次性”对象,其中一些可能已经被清理up,没问题;对已经调用了 Dispose 的对象的任何引用将仍然是对已经调用了 Dispose 的对象的引用。 (2认同)
  • @JCoombs:相比之下,非托管资源通常没有这样的保证。如果对象 `Fred` 拥有文件句柄 #42 并关闭它,系统可能会将相同的编号附加到某些文件句柄,该文件句柄提供给其他实体。在这种情况下,文件句柄 #42 不会指 Fred 关闭的文件,而是指其他实体正在使用的文件;如果`Fred` 再次尝试关闭句柄#42 将是灾难性的。尝试 100% 可靠地跟踪一个非托管对象是否已被释放是可行的。试图跟踪多个对象要困难得多。 (2认同)
  • @JCoombs:如果每个非托管资源都被放置在它自己的包装器对象中,它只控制其生命周期,那么外部代码不知道资源是否已被释放,但知道它应该是 *如果它没有被释放已经*,可以安全地要求包装对象释放它;包装器对象将知道它是否已经这样做并且可以执行或忽略请求。GC 保证对包装器的引用始终是对包装器的有效引用这一事实是*非常有用的*保证。 (2认同)

Gen*_*Ziy 42

最终确定

  • 终结器应始终是protected,不是public或者private不能直接从应用程序的代码中调用该base.Finalize方法,同时,它可以调用该方法
  • 终结器应仅释放非托管资源.
  • 该框架不保证终结器将在任何给定实例上执行.
  • 永远不要在终结器中分配内存或从终结器中调用虚方法.
  • 避免同步并在终结器中引发未处理的异常.
  • 终结器的执行顺序是非确定性的 - 换句话说,您不能依赖终结器中仍然可用的另一个对象.
  • 不要在值类型上定义终结器.
  • 不要创建空的析构函数.换句话说,除非你的类需要清理非托管资源,否则你永远不应该显式定义析构函数,如果你确实定义了一个,它应该做一些工作.如果稍后您不再需要清除析构函数中的非托管资源,请将其完全删除.

部署

  • 实现IDisposable对每一个都有一个终结类型
  • 在调用Dispose方法后,确保对象不可用.换句话说,避免Dispose在调用方法后使用对象.
  • 完成后,请调用Dispose所有IDisposable类型
  • 允许Dispose多次调用而不会引发错误.
  • Dispose使用该GC.SuppressFinalize方法抑制稍后从方法中调用终结器
  • 避免创建一次性值类型
  • 避免在Dispose方法中抛出异常

处理/最终模式

  • Microsoft建议您同时实现Dispose,并Finalize与非托管资源工作时.Finalize即使开发人员忽略了Dispose明确调用该方法,实现也会在对象被垃圾回收时仍然会释放资源.
  • 清除Finalize方法和Dispose方法中的非托管资源.另外,从Dispose方法中为该类中的任何.NET对象(具有非托管资源作为其成员)调用该Dispose方法.

  • 我到处读到同样的答案,但我仍然无法理解每个人的目的是什么.我只读规则后的规则,仅此而已. (16认同)

Bhu*_*ale 31

当此对象不再使用时,GC会调用Finalize.

Dispose只是一个普通的方法,这个类的用户可以调用它来释放任何资源.

如果用户忘记调用Dispose,并且该类已实现Finalize,则GC将确保调用它.

  • 最干净的答案 (3认同)

Mir*_*mvs 17

MCSD认证工具包(考试70-483)第193页中有一些关键词:

析构函数≈(它几乎等于)base.Finalize(),析构函数被转换为Finalize方法的覆盖版本,该方法执行析构函数的代码,然后调用基类的Finalize方法.然后它完全不确定,你无法知道什么时候会被调用因为取决于GC.

如果类不包含托管资源且没有非托管资源,则不需要实现IDisposableor具有析构函数.

如果类只有托管资源,它应该实现IDisposable,但它不需要析构函数.(当析构函数执行时,您无法确定托管对象是否仍然存在,因此无论如何都无法调用其Dispose方法.)

如果类只有非托管资源,则需要实现IDisposable,并且在程序不调用Dispose时需要析构函数.

Dispose方法必须安全运行多次.您可以通过使用变量来跟踪它之前是否已运行来实现.

Dispose方法应释放托管和非托管资源.

析构函数应该只释放非托管资源.(当析构函数执行时,您无法确定托管对象是否仍然存在,因此无论如何都无法调用其Dispose方法.)

释放资源后,析构函数应调用GC.SuppressFinalize,因此对象可以跳过终结队列.

具有非托管和托管资源的类的实现示例:

using System;

class DisposableClass : IDisposable
{
    // A name to keep track of the object.
    public string Name = "";

    // Free managed and unmanaged resources.
    public void Dispose()
    {

        FreeResources(true);
    }

    // Destructor to clean up unmanaged resources
    // but not managed resources.
    ~DisposableClass()
    {
        FreeResources(false);
    }

    // Keep track if whether resources are already freed.
    private bool ResourcesAreFreed = false;

    // Free resources.
    private void FreeResources(bool freeManagedResources)
    {
        Console.WriteLine(Name + ": FreeResources");
        if (!ResourcesAreFreed)
        {
            // Dispose of managed resources if appropriate.
            if (freeManagedResources)
            {
                // Dispose of managed resources here.
                Console.WriteLine(Name + ": Dispose of managed resources");
            }

            // Dispose of unmanaged resources here.
            Console.WriteLine(Name + ": Dispose of unmanaged resources");

            // Remember that we have disposed of resources.
            ResourcesAreFreed = true;

            // We don't need the destructor because
            // our resources are already freed.
            GC.SuppressFinalize(this);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案!但我认为这是错误的:“析构函数应该调用 GC.SuppressFinalize”。相反,公共 Dispose() 方法不应该调用 GC.SuppressFinalize 吗?请参阅:https://docs.microsoft.com/en-us/dotnet/api/system.gc.suppressfinalize?view=netframework-4.7.2 调用此方法可防止垃圾收集器调用 Object.Finalize(由析构函数)。 (2认同)

JP *_*oto 7

99%的时间,你不必担心.:)但是,如果您的对象包含对非托管资源(例如窗口句柄,文件句柄)的引用,则需要为托管对象提供释放这些资源的方法.Finalize给出了释放资源的隐式控制.它由垃圾收集器调用.Dispose是一种明确控制资源释放的方法,可以直接调用.

关于垃圾收集的主题还有很多东西需要学习,但这是一个开始.

  • 我敢肯定超过1%的C#应用​​程序使用数据库:你*担心IDisposable SQL的东西. (5认同)
  • 同意,但这正是重点.使用模式为您隐藏了对Dispose的调用. (2认同)

Jus*_*tin 5

终结器用于隐式清理 - 每当一个类管理绝对必须清理的资源时你应该使用它,否则你会泄漏句柄/内存等......

正确地实现终结器是非常困难的,应尽可能避免 - SafeHandle类(在.Net v2.0及更高版本中)现在意味着您很少(如果有的话)需要再实现终结器.

IDisposable接口用于显式清理并且更常用 - 您应该使用它来允许用户在完成使用对象时显式释放或清理资源.

请注意,如果您有终结器,那么您还应该实现该IDisposable接口,以允许用户比对象被垃圾收集时更快地显式释放这些资源.

请参阅DG更新:处理,完成和资源管理,我认为这是关于终结器和最佳建议的最佳和最完整的建议IDisposable.