IDisisposable应该级联应用吗?

时间:2009-09-22 17:50:55

标签: c# .net idisposable

这是一个相当基本的问题,但我仍然在努力解决它。

当您希望在对象最终被垃圾回收之前允许对象的用户释放底层资源(例如套接字等)时,实现了IDisposable。

当我有一个持有DbConnection(实现IDisposable)的类时,我的类是否也需要实现IDisposable并将调用链接到DbConnection或它拥有的任何其他IDisposable对象?否则,只有在我的类为GarbageCollected时才会释放DbConnections资源,从而删除它对连接的引用,GC将最终确定DbConnection。

9 个答案:

答案 0 :(得分:9)

是的,如果你控制一次性物品,你总是实施IDisposable。 始终即可。如果你不这样做,你的代码就不会破坏,但是如果不这样做,它就会破坏使用一次性物品的目的。

GC优化的一般规则是:

  • 任何控制不由GC管理的对象的类都必须实现终结器(通常也应该实现IDisposable)。这是“顶级”一次性课程通常来自的地方 - 他们通常将手柄控制到窗口,插座,互斥体或者你拥有的东西。
  • 任何实例化IDisposable成员的类都应该实现IDisposable本身,并正确处理其成员的Dispose()。
  • 任何实例化IDisposeable对象的函数都应该在使用它时正确地Dispose()。不要让它超出范围。

如果您正在为自己编写应用程序,这些规则可能会被忽略或忽略,但在向其他人分发代码时,您应该专业并遵守规则。

这里的逻辑是,当你在GC视图之外控制内存时,GC引擎无法正确管理你的内存使用情况。例如,在.NET堆上,您可能只有一个4字节的指针,但在非托管的域中,您可以指向200 MB的内存。 GC引擎在你有几十个之前不会尝试收集它们,因为它只看到几个字节;而在现实世界中,它看起来很像内存泄漏。

因此,规则是,当您使用它时,非托管内存应立即释放(IDisposable链为您执行此操作),而GC引擎会随时释放托管内存。

答案 1 :(得分:6)

是的,如果需要处理它使用的任何对象,那么你的类需要是IDisposable。一个例子是StreamReader。它实现了IDisposable,因此它可以处理其关联的流对象。

答案 2 :(得分:3)

如果我理解你的问题,你就有一个使用DbConnection的类。您希望确保在完成DbConnection或处理类时正确处理DbConnection。有几种方法可以实现这一目标。

如果在方法中使用数据库连接作为局部变量,则可以使用using(){}语句。

using (SqlConnection sqlConnection = new SqlConnection(connStr))
{
...do stuff with connection here
}

using(){}语句自动调用()中声明的对象上的Dispose()。 (它还要求()中声明的对象实现IDisposable以确保它们可以被处置。

如果您正在使用DbConnection作为在对象构造期间初始化的私有变量或其他一些初始化方法,那么您可能希望自己实现IDisposable,然后在Dispose()方法中调用_dbConnection.Dispose()。这样,当您的对象被处置时,数据库连接对象也将被处置。

public class MyDALObj : IDisposable
{

public MyDalObj()
{
... create _dbConn object ...
}

public void Dispose()
{
_dbConn.Dispose();
}

private DbConnection _dbConn;
}

答案 3 :(得分:3)

你应该这样做,因为这是你班级用户确保内部资源得到妥善处理的唯一途径。

但是,用于Dispose()的模式可能与通常编写的模式略有不同,在这种情况下,因为您不必区分非托管资源和托管资源(您的封装资源始终被视为“托管” “资源”。

我写了一篇关于这个特定主题的详细博文 - Encapsulating IDisposable Resources

答案 4 :(得分:2)

有两种不同的情况:

  1. 您的对象赋予要使用的对象引用,可以通过构造函数参数或属性,并且此对象实现IDisposable。
  2. 您的对象构造实现IDisposable的对象的实例。
  3. 在第二种情况下,您的对象负责所涉及的资源,因此您的对象必须实现IDisposable,并且在处置时,您应该处置您构造的对象。

    你的DbConnection属于第二种情况,所以是的,你的对象应该实现IDisposable,然后处理连接。

    在第一种情况下,您需要决定以下三种解决方案:

    1. 您的对象仅引用外部对象。您的对象不应该丢弃此外部对象。对于这种情况,您不需要实现IDisposable(也就是说,对于此特定对象,如果您还在内部构造一次性对象,则回到上面的第二种情况)。
    2. 您的对象负责外部对象。在这种情况下,即使您的对象不是构造此外部对象的对象,您也会回到第二种情况。在这里,您实现IDisposable,并处理您给出的对象。
    3. 您可以通过外部世界的方式告诉您前两个解决方案中的哪一个。例如,构造函数可能被赋予连接,并且布尔参数(或理想情况下是枚举值)告诉构造函数您的对象现在是否拥有所提供的连接。在这里你还需要实现IDisposable,但是在Dispose方法中,你需要检查所有权,如果你拥有它,只需要处理提供的连接。
    4. 这是很多文字,所以让我总结一下:

      1. 您拥有的物品,需要处置。
      2. 你没有的物品,你没有处置。
      3. 还有第三种情况,听起来并不像你所拥有的那样,但仍然如此。

        如果在本地构建,使用和丢弃一个对象,在单个方法中,而不传递它或将其存储在类的字段中,则使用using语句,就像这样:

        using (IDbConnection conn = ....())
        {
        }
        

答案 5 :(得分:0)

这当然是最好的做法,尤其是在处理繁重/非托管对象时。

编辑:最佳做法,但不是强制性的。

答案 6 :(得分:0)

由于我们永远不知道GC何时会收集对象,因此我们使用IDisposable接口在垃圾收集对象之前有机会有意释放非托管资源。 如果在收集之前未处置一次性对象,则在退出AppDomain之前可能不会释放其资源。 这几乎是一个不成文的规则,每个引用IDisposable对象的对象都应该是IDisposable本身,并在自己的Dispose方法中调用其IDisposable引用的Dispose方法。

答案 7 :(得分:0)

当然,如果使用C ++ / CLI,您可以删除IDisposable的大量(重新)实现成本,并获得与托管堆上对象的确定性最终化非常接近的东西。这是一种经常(我发现)被忽视的一种语言方面,很多人似乎都只是委托给“只用胶水代码”的箱子。

答案 8 :(得分:0)

使用Dispose提供显式控制时,应使用Finalize方法提供隐式清理。如果程序员无法调用Dispose,则Finalize提供备份以防止资源永久泄漏。

我认为实现这一点的最佳方法是使用Dispose和Finalize方法的组合。 您可以找到更多Here