40

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

怎么处理这个?

编辑:

查看相关问题侧边栏,我发现了另一个问题,它明确表示Dispose()无论如何应该由对象的终结器调用。因此,即使您从未自己调用它,它最终也应该会发生,这意味着如果您不使用它就不会发生内存泄漏using(我想这是我一直担心的问题)。唯一需要注意的是,垃圾收集器不知道对象作为非托管的东西持有多少额外的内存,因此它无法准确知道通过收集对象将释放多少内存。这将导致垃圾收集器的性能不如通常理想。

简而言之,如果我错过了一个using. 我只是希望某些东西至少会产生一个警告。

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

编辑:

好吧,好吧,别吵了。这是超级骗子的全能戏剧花栗鼠级别,重要的是要打电话Dispose(),否则我们都会

现在。鉴于此,为什么这么容易——见鬼,为什么甚至允许——做错事?你必须不遗余力地做正确的事。像其他所有事情一样这样做会导致世界末日(显然)。封装这么多,是吧?

[离开,厌恶]

4

12 回答 12

24

FxCop可能会有所帮助(尽管它没有发现我刚刚对其进行的测试);但是是的:您应该检查一下。IDisposable只是系统中如此重要的一部分,您需要养成这种习惯。使用智能感知来寻找.D是一个好的开始(虽然并不完美)。

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

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

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

using (int i = 5) {}

Error   1   'int': type used in a using statement must be implicitly convertible to 'System.IDisposable'    
于 2008-10-31T21:11:00.817 回答
15

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

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

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

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

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

  • IntelliSense(如果它有一个Dispose()或一个Close()方法)
  • MSDN
  • 反射器
  • 编译器(如果它没有实现IDisposable你会得到一个编译器错误)
  • 常识(如果感觉你应该在完成后关闭/释放对象,那么你可能应该
  • 语义(如果有一个Open(),可能有一个对应Close()的应该被调用)
  • 编译器。试着把它放在一个using声明中。如果它没有实现 IDisposable,编译器会产生一个错误。

将处置模式视为与范围生命周期管理有关的全部。您希望尽可能最后获得资源,尽可能快地使用并尽快释放。using 语句通过确保Dispose()即使有异常也会进行调用来帮助做到这一点。

于 2008-11-01T03:38:35.447 回答
4

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

这里的问题是您不能总是通过将 IDisposable 包装在一个using块中来处理它。有时你需要让物体停留更长时间。在这种情况下,您必须Dispose自己显式调用它的方法。

一个很好的例子是一个类使用私有 EventWaitHandle(或 AutoResetEvent)在两个线程之间进行通信,并且您希望在线程完成后处理 WaitHandle。

因此,它不像某些工具那样简单,只是检查您是否只在using块内创建 IDisposable 对象。

于 2009-03-19T17:48:51.180 回答
4

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

很多人说这IDisposable仅适用于非托管资源,这仅取决于您如何定义“资源”。您可以实现读/写锁IDisposable,然后“资源”是对代码块的概念访问。您可以在构造函数中将光标更改为沙漏并返回到先前保存的值的对象IDispose,然后“资源”是更改后的光标。我会说,当您希望在离开范围时发生确定性操作时,无论范围如何离开,您都可以使用 IDisposable,但我不得不承认,它远不如说“它用于管理非托管资源管理”那么吸引人。

另请参阅有关为什么 .NET 中没有 RAII的问题。

于 2008-11-02T17:04:23.010 回答
3

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

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

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

这就是为什么仅仅依靠 GC 来处理是不够的。内存使用不一定与您获取(并且不释放)的非托管资源的数量相关。因此,这些资源可能在调用 GC 之前很久就耗尽了。

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

于 2008-11-02T15:49:30.277 回答
2

我真的没有什么可以添加到 Using 块的一般用途中,只是想为规则添加一个例外:

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

于 2008-11-05T09:10:32.467 回答
1

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

于 2008-11-02T18:15:22.230 回答
1

我主要在这种情况下使用 using 块:

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

为了争论,假设我的方法有 11 个退出点,其中 10 个在这个 using 块内,1 个在它之后(这在我编写的一些库代码中很典型)。

由于在退出 using 块时对象会自动释放,因此我不需要 10 次不同的 .Dispose() 调用——它只是发生了。这导致代码更简洁,因为它现在不再被 dispose 调用混乱(在这种情况下,代码行数减少了约 10 行)。

如果有人在我之后更改代码,如果他们忘记调用 dispose,引入 IDisposable 泄漏错误(这可能会很耗时)的风险也较小,因为使用块没有必要。

于 2008-11-02T19:34:18.030 回答
0

始终尝试使用“使用”块。对于大多数对象,它并没有太大的区别,但是我遇到了一个最近的问题,我在一个类中实现了一个 ActiveX 控件,并且除非正确调用了 Dispose,否则没有优雅地清理。最重要的是,即使它似乎没有太大的不同,也要尝试正确地去做,因为有时它会有所作为。

于 2008-11-01T03:13:26.183 回答
0

根据此链接CodeRush 加载项将在您键入时实时检测并标记本地 IDisposable 变量未清理的时间。

可以在你的任务中遇到你。

于 2010-02-10T05:39:17.967 回答
0

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

于 2008-10-31T21:16:38.387 回答
-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语句,该对象最终也会被垃圾收集并执行适当的销毁逻辑。您可以停止线程、结束连接、保存数据,无论您需要什么(在此示例中,我取消订阅远程服务并在需要时执行远程删除调用)

[编辑] 显然,尽快调用 Dispose 有助于提高应用程序性能,并且一种很好的做法。但是,感谢我的示例,如果您忘记调用 Dispose,最终将调用它并清理对象。

于 2011-01-19T15:39:58.633 回答