处理.NET IDisposable对象

时间:2008-10-31 21:08:54

标签: c# .net visual-studio idisposable using-statement

我在C#工作,并且我一直非常松懈地使用using块来声明实现IDisposable的对象,这显然是你应该做的。但是,我没有看到一种简单的方法来了解我何时滑倒。 Visual Studio似乎没有以任何方式表明这一点(我只是错过了什么?)。我每次申报任何东西时都应该检查帮助,并逐渐建立一个百科全书记忆,对象是哪些,哪些不是一次性的?似乎没有必要,痛苦和容易出错。

如何处理这个问题?

修改

查看相关问题边栏,我发现another question明确表示Dispose()应该被对象的终结器调用。所以,即使你自己也没有自己调用它,它最终应该会发生,这意味着如果你不使用using(这就是我认为我一直都很担心的话)你就不会有内存泄漏。唯一需要注意的是,垃圾收集器不知道对象作为非托管内容持有多少额外内存,因此无法准确了解通过收集对象将释放多少内存。这将导致垃圾收集器的性能低于平常。

简而言之,如果我错过using,那不是世界末日。我只是希望某些东西能产生至少一个警告。

(偏离主题:为什么链接到另一个问题没有特殊的降价?)

修改

好的,好的,别叫吵了。完全激活dramatic-chipmunk - 级别 重要 ,以致电Dispose(),或者我们所有人。< / p>

现在。鉴于此,为什么这么容易 - 地狱,为什么甚至允许 - 做错了?你必须尽力去做正确的事。像其他一切一样做它会导致世界末日(显然)。封装这么多,呵呵?

[Stalks off,disgusted]

12 个答案:

答案 0 :(得分:24)

FxCop 可能有帮助(虽然它没有发现我刚刚解雇的测试);但是是的:你打算检查一下。 IDisposable只是你需要养成这种习惯的系统的重要组成部分。使用intellisense寻找.D是一个好的开始(虽然不完美)。

但是,熟悉需要处理的类型并不需要很长时间;通常涉及任何外部(连接,文件,数据库)的任何东西,例如。

ReSharper也做了这项工作,提供了“投入使用构造”选项。它不会将它作为错误提供,但是......

当然,如果你不确定 - 试试 using它:如果你是偏执狂,编译器会嘲笑你:

using (int i = 5) {}

Error   1   'int': type used in a using statement must be implicitly convertible to 'System.IDisposable'    

答案 1 :(得分:15)

如果一个对象实现了IDisposable接口,那么这是有原因的,你打算调用它,它不应被视为可选。最简单的方法是使用using块。

Dispose()并非仅由对象的终结器调用,事实上,许多对象将实现Dispose()但没有终结器(完全有效)。

处置模式背后的整个想法是,您提供了一种有点确定性的方法来释放由对象(或其继承链中的任何对象)维护的非托管资源。如果没有正确调用Dispose(),您绝对可以运行内存泄漏(或任何其他问题)。

Dispose()方法与析构函数无关。你在.NET中最接近析构函数的是终结器。 using语句不执行任何释放...事实上,调用Dispose()不会在托管堆上执行任何释放;它只释放已分配的非托管资源。在GC运行并收集分配给该对象图的内存空间之前,托管资源不会被真正释放。

确定类是否实现IDisposable的最佳方法是:

  • IntelliSense(如果它具有Dispose()Close()方法)
  • MSDN
  • 反射
  • 编译器(如果它没有实现IDisposable,则会出现编译器错误)
  • 常识(如果感觉就像你应该在完成后关闭/释放对象,那么你可能应该
  • 语义(如果有Open(),可能会有相应的Close()应该被调用)
  • 编译器。尝试将其放在using语句中。如果它没有实现IDisposable,编译器将生成错误。

将dispose模式视为范围生命周期管理。您希望尽可能快地获取资源,尽可能快地使用,并尽快发布。 using语句通过确保即使有例外也可以调用Dispose()来帮助完成此操作。

答案 2 :(得分:4)

这就是为什么(恕我直言)C ++的RAII优于.NET的using语句。

很多人说IDisposable仅适用于未受管理的资源,这只是根据您定义“资源”的方式而定。您可以使用实现IDisposable的读/写锁,然后“资源”是对代码块的概念性访问。您可以在构造函数中将光标更改为沙漏,然后返回到IDispose中先前保存的值,然后“资源”是更改的光标。我想说当你想要在离开范围时确定性行动时,无论范围如何,你都会使用IDisposable,但我不得不承认它比说“它用于管理非托管资源管理”要小得多。

另请参阅有关why there's no RAII in .NET的问题。

答案 3 :(得分:4)

  

简而言之,如果我错过了使用它,那不是世界末日。我只是希望某些东西能产生至少一个警告。

这里的问题是你不能总是通过将其包装在using块中来处理IDisposable。有时您需要将对象挂起一段时间。在这种情况下,您必须自己明确调用其Dispose方法。

这方面的一个很好的例子是,class uses a private EventWaitHandle (or an AutoResetEvent)在两个线程之间进行通信,并且您希望在线程完成后释放WaitHandle。

所以它并不像某些工具那么简单,只是检查你是否只在using块中创建了IDisposable对象。

答案 4 :(得分:3)

@Atario,不仅接受的答案是错的,你自己的编辑也是如此。想象一下以下情况(在Visual Studio 2005的一个CTP中实际发生):

对于绘制图形,您可以在不处理它们的情况下创建笔。笔不需要大量内存,但内部使用GDI +句柄。如果您不丢弃笔,则不会释放GDI +手柄。如果您的应用程序不是内存密集型的,那么在没有调用GC的情况下可以通过一段时间。但是,可用的GDI +句柄数量受到限制,很快,当您尝试创建新笔时,操作将失败。

事实上,在Visual Studio 2005 CTP中,如果您使用的时间足够长,所有字体都会突然切换到“系统”。

这正是为什么不够依赖GC进行处置的原因。内存使用量不一定与您获取(并且不释放)的非托管资源的数量相关联。因此,在调用GC之前很久就会耗尽这些资源。

此外,当然还有这些资源可能具有的副作用的整个方面(例如访问锁定)阻止其他应用程序正常工作。

答案 5 :(得分:2)

我真的没有任何东西可以添加到使用块的一般用途,但只是想在规则中添加一个例外:

任何实现IDisposable的对象显然不应该在Dispose()方法中抛出异常。这完美地工作到WCF(可能还有其他),现在可能在Dispose()期间由WCF通道抛出异常。如果在Use块中使用它时会发生这种情况,则会导致问题,并且需要实现异常处理。这显然需要更多的内部工作知识,这就是为什么微软现在建议不要在使用块中使用WCF频道(抱歉找不到链接,但谷歌有很多其他结果),即使它实现了IDisposable ..只是为了让事情更多复杂!

答案 6 :(得分:1)

不幸的是,FxCop或StyleCop似乎都没有对此发出警告。正如其他评论者所提到的那样,确保调用dispose通常非常重要。如果我不确定,我总是检查对象浏览器(Ctrl + Alt + J)以查看继承树。

答案 7 :(得分:1)

我主要使用块来实现这种情况:

我正在使用一些外部对象(在我的情况下通常是IDisposable包装的COM对象)。对象本身的状态可能会导致它抛出一个异常或它如何影响我的代码可能会导致我抛出一个异常,也许会在很多不同的地方抛出异常。一般来说,我不相信当前方法之外的任何代码都不能表现自己。

为了论证,假设我的方法有11个退出点,其中10个位于此使用块内,1个位于其后面(这在我编写的某些库代码中可能是典型的)。

由于在退出using块时对象会自动处理掉,所以我不需要进行10次不同的.Dispose()调用 - 它就会发生。这样可以实现更清晰的代码,因为它现在不再使用dispose调用(在这种情况下减少约10行代码)。

如果有人在忘记调用dispose之后更改代码,那么引入IDisposable泄漏错误的风险也会降低(查找起来可能很费时),因为没有必要使用using块。

答案 8 :(得分:0)

与Fxcop(它们相关)一样,VS中的代码分析工具(如果你有一个较高版本的版本)也会发现这些情况。

答案 9 :(得分:0)

始终尝试使用“使用”块。对于大多数对象,它没有太大的区别但是我遇到了一个最近的问题,我在类中实现了ActiveX控件,并且没有正常清理,除非正确调用Dispose。最重要的是,即使它似乎没有太大差别,尝试正确地做,因为有一段时间它会产生影响。

答案 10 :(得分:0)

根据this linkCodeRush add-in将在您键入时实时检测并标记何时未清除本地IDisposable变量。

可以在你的任务中途见到你。

答案 11 :(得分:-1)

我没有明白你的问题。由于垃圾收集器,几乎不可能发生内存泄漏。但是,您需要一些强大的逻辑。

我用来创建像这样的IDisposable类:

public MyClass: IDisposable
{

    private bool _disposed = false;

    //Destructor
    ~MyClass()
    { Dispose(false); }

    public void Dispose()
    { Dispose(true); }

    private void Dispose(bool disposing)
    {
        if (_disposed) return;
        GC.SuppressFinalize(this);

        /* actions to always perform */

        if (disposing) { /* actions to be performed when Dispose() is called */ }

        _disposed=true;
}

现在,即使您错过使用using语句,该对象最终也会被垃圾收集并执行正确的销毁逻辑。您可以停止线程,结束连接,保存数据,无论您需要什么(在this example中,我取消订阅远程服务并在需要时执行远程删除调用)

[编辑]显然,尽快调用Dispose有助于提高应用程序性能, 是一种很好的做法。但是,多亏了我的例子,如果你忘记调用Dispose,它最终会被调用并清理对象。