【问题标题】:What is this IDisposable doing for me?这个 IDisposable 对我有什么作用?
【发布时间】:2012-01-13 14:53:02
【问题描述】:

我正在开发一个“学习计划”,并在我的学习中使用 Code Rush 重构工具。随着 Code Rush 的最新更新,它一直建议在我的程序中实现 IDisposable。我知道 MSDN 关于 IDisposable 的说法,并且我对它的作用有一个真正的基本了解,但是因为我不知道实现它的所有含义,所以我一直忽略了这个建议。今天我决定进一步了解它并接受建议。

这是它添加到我的程序中的内容。

class Program : IDisposable
{
    static Service _proxy;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
            if (_proxy != null)
            {
                _proxy.Dispose();
                _proxy = null;
            }
    }

    ~Program()
    {
        Dispose(false);
    }

所以我的问题是这样的。这是否做了我需要做的一切来获得 IDisposable 的优势,还是我需要在代码中做一些事情才能使它工作?我在上面设置了一个断点,并且从未通过调试器到达它,所以要么不需要它,要么我没有按照预期的方式使用它。有人可以解释一下这对我有什么作用或我应该如何使用它以便它对我有用吗?

【问题讨论】:

  • 看起来是对的。 IDisposable 的想法是您的对象将引用非托管资源。由于 GC 不会清理非托管资源,因此对象需要说..“等等,我必须在被销毁之前做一些房屋清理”。每当您的对象使用非托管代码/调用或包含实现 IDisposable 的对象时,您还应该实现 IDisposable 并在任何实现它的对象上调用 Dispose() 和/或清理您的非托管代码。
  • 坦率地说,在这种情况下,Code Rush 在这里做得很差。此代码建议可能会引入非常糟糕的行为 - 这是他们建议中的错误。
  • @tipx:追踪 Dispose(false) 的实际作用。
  • 各位,谢谢你的笔记。我们将尽快修复实施 IDisposable 功能。
  • 我们将在下一次更新中解决这个问题,我们还将改进对字段的分析,以确保该字段绝对拥有其内容并且它们不会在其他任何地方共享(引用/传递给) .

标签: c# refactoring idisposable coderush


【解决方案1】:

在这种情况下,CodeRush 建议您实现IDisposable,因为您的类封装了IDisposable 资源(它看到的是_proxy,尽管这并不完全是一件好事,因为它是静态的)。 Code Rush 认为您正在使用的类型应该被显式清理,但您没有提供通过您的类来执行此操作的方法。

话虽如此,IDisposable 很棘手 - 这是生成的代码并不是真正好的实现的一种情况(即使 _proxy 是一个实例变量)。在这种情况下,我建议不要使用析构函数。这将导致 GC 中的性能问题,在这种情况下,对安全性没有帮助,因为封装的资源应该处理您忘记为您调用 Dispose() 的情况。有关详细信息,请参阅my series on IDisposable,尤其是encapsulating an IDisposable class

此外,鉴于上面的代码,这个类不应该实现IDisposable(除非有其他原因),因为_proxy资源是static。至少在一般情况下,从实例中释放静态资源可能会导致问题。 (在这种情况下,这显然没有问题,但这不是一个好习惯......)通常,静态变量的生命周期与实例成员的生命周期非常不同,因此自动处置它是不合适的。

【讨论】:

  • 完全正确——99% 的情况下,您不需要 IDisposable 的析构函数,因为您只是封装了一个已经具有析构函数的资源。
  • @JoelCoehoorn 在这种情况下更糟,因为_proxy静态的 - 处理它是完全不合适的。
  • 推荐使用终结器,以防子类需要使用非托管清理覆盖 Dispose(bool) 方法。当然,派生类可以只从它自己的终结器中调用 Dispose(false),这可以解决问题,尽管它与“官方”模式略有不同。就个人而言,我更喜欢将课程标记为密封并完全避免该问题。
  • @DanBryant 是的 - 在我看来,子类直接添加 unmanaged 资源是非常不合适的。它使用的资源应该包装在它自己的托管处理程序中,这消除了这个问题......但是,如果你走那条路,那么它可以做自己的析构函数,正如你所提到的,这将更“为游戏付费”
  • @Dan Bryant:“官方”模式是派生类添加一个终结器,必要时调用 Dispose(false),尽管我想不出任何 从非平凡基类派生的类应该添加清理终结器的情况。派生类添加一个终结器可能是合理的,其唯一目的是在缺乏适当的处置时大声疾呼,但我想不出任何不应该由封装类处理清理的场景。
【解决方案2】:

在正确编写的程序中,在任何给定时间,对于可能具有 IDisposable 的有意义实现的每个对象,都会有一些实体负责确保 IDisposable.Dispose 将在该对象上的某个时间被调用该实例的最后一次“真正使用”及其最终放弃。通常,如果对象 Foo 将持有对实现 IDisposable 的对象的引用,则至少应适用以下一种情况:

  1. 其他一些对象也将至少在 Foo 需要它的时候持有该引用,并且会负责对其调用 Dispose,因此 Foo 应该让其他对象负责 Dispose。
  2. 持有引用的对象将是最后使用相关 IDisposable 对象的对象;如果 Foo 不调用 Dispose,则其他都不会。在这种情况下,Foo 必须确保在不再需要它 (Foo) 时以及在它被放弃之前调用其他对象的 Dispose 方法。处理此问题的最惯用方法是让 Foo 实现 IDisposable.Dispose,并让其 Dispose 方法在它持有最后有用引用的 IDisposable 对象上调用 Dispose。

在某些情况下,类设计器可能不知道其类是否将保存对 IDisposable 对象的最后一个有用引用。在某些情况下,可以通过让类构造函数指定传递给构造函数的 IDisposable 是“借出”还是“给定”来解决此问题。其他情况可能需要使用引用计数包装器或其他复杂技术。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 2011-02-27
    • 1970-01-01
    • 1970-01-01
    • 2013-12-10
    • 2018-09-04
    • 1970-01-01
    相关资源
    最近更新 更多