IDisisposable应该级联应用吗?

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

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

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

是的,如果你控制一次性物品,你总是可以实现IDisposable。 总是 。 如果你不这样做,你的代码就不会破坏,但如果不这样做,它就会失去拥有一次性物品的目的。

GC优化的一般规则是:

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

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

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

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

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

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

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

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

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

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

public class MyDALObj : IDisposable
{

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

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

private DbConnection _dbConn;
}

您应该这样做,因为这是您的类用户确保正确处理内部保留资源的唯一方法。

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

我写了一篇关于这个特定主题的详细博客文章 – 封装IDisposable资源 。

有两种不同的场景:

  1. 通过构造函数参数或属性为您的对象提供要使用的对象引用,并且此对象实现IDisposable。
  2. 您的对象构造实现IDisposable的对象的实例。

在第二种情况下,您的对象负责所涉及的资源,因此您的对象必须实现IDisposable,并且在处置时,您应该处置您构造的对象。

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

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

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

那是很多文字,所以让我总结一下:

  1. 你拥有的物品,你需要处置。
  2. 你没有的物体,你没有处置。

还有第三种情况,它听起来并不像你所拥有的那样,但仍然如此。

如果您在单个方法中本地构造,使用和丢弃对象,而不传递它或将其存储在类的字段中,则使用using语句,如下所示:

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

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

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

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

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

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

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