.net IDisposable是否应级联应用?

t3psigkw  于 2022-11-19  发布在  .NET
关注(0)|答案(9)|浏览(147)

这是一个相当基本的问题,但我仍然有点纠结。
IDisposable,当您想要让对象的使用者在对象最终被垃圾回收之前释放基础资源(例如,通信端等)时。
当我有一个保存DbConnection(实现IDisposable)的类时,我的类是否也需要实现IDisposable,并将调用链接到DbConnection或它拥有的任何其他IDisposable对象?否则,只有当我的类被垃圾回收时,DbConnections资源才会被释放,从而丢弃它对连接的引用,GC将最终完成DbConnection

5uzkadbs

5uzkadbs1#

是的,如果您控制可处置对象,您总是实现IDisposable。总是。如果您不这样做,您的代码将不会中断,但是如果您不这样做,它将破坏使用可处置对象的目的。
GC优化的一般规则是:

  • 任何控制不受GC管理的对象的类都必须实现终结器(通常也应该实现IDisposable)。这就是“顶级”可处置类通常的来源--它们通常控制窗口、套接字、互斥体或其他对象的句柄。
  • 任何示例化IDisposable成员的类都应实现IDisposable本身,并正确实现其组成部分的Dispose()。
  • 任何示例化IDisposeable对象的函数都应该在使用完后正确地使用Dispose()函数,不要让它脱离作用域。

如果您是为自己编写应用程序,这些规则可能会被扭曲或忽略,但在向其他人分发代码时,您应该是专业的,并遵循这些规则。
这里的逻辑是,当您在GC视图之外控制内存时,GC引擎不能正确地管理内存使用。例如,在.NET堆上,您可能只有一个4字节的指针,但在非托管区域,您可能有200 MB的内存被指向。GC引擎不会尝试收集这些内存,直到您有几十个,因为它看到的只是几个字节;而在真实的世界中,它看起来很像内存泄漏。
因此,规则是,非托管内存应该在您使用完后立即被释放(IDisposable链会为您做这件事),而托管内存则在GC引擎找到它时被释放。

dpiehjr4

dpiehjr42#

是的,如果你的类需要释放它所使用的任何对象,它就需要是IDisposable。一个这样的例子是StreamReader。它实现了IDisposable,这样它就可以释放它关联的流对象。

qrjkbowd

qrjkbowd3#

如果我没理解错你的问题,你有一个使用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; }

kzmpq1sx

kzmpq1sx4#

您应该这样做,因为这是类的用户确保内部持有的资源被正确释放的唯一方法。
但是,Dispose()使用的模式可能与通常编写的模式略有不同,在这种情况下,因为您不必区分非托管资源和托管资源(封装的资源始终被视为“托管”资源)。
我写了一篇关于这个特定主题的详细博文-Encapsulating IDisposable Resources

vltsax25

vltsax255#

有两种不同的方案:
1.通过构造函数参数或属性,为您的对象 * 提供 * 一个要使用的对象引用,并且此对象实现IDisposable。
1.您的对象会建构实作IDisposable之对象的执行严修。
在第二种情况下,您的对象负责所涉及的资源,因此您的对象必须实现IDisposable,并且在释放时,您应该释放所构造的对象。
您的DbConnection福尔斯第二种情况,所以是的,您的对象应该实现IDisposable,然后释放连接。
在第一种情况下,您需要决定以下三种解决方案:
1.你的对象只引用了一个外部对象。你的对象不应该释放这个外部对象。在这种情况下,你不需要实现IDisposable(也就是说,对于这个特定的对象,如果你也在内部构造了一个可释放的对象,你就回到了上面的第二种情况)。
1.你的对象负责外部对象。在这个例子中,你回到了第二个例子,即使你的对象不是构造这个外部对象的对象。这里你实现了IDisposable,并释放你所得到的对象。
1.您可以实现一种方法,让外界告诉您选择前两个解决方案中的哪一个。(或者理想情况下是一个枚举值),它告诉构造函数您的对象现在是否拥有所提供的连接。您需要检查所有权并仅处置您拥有的连接。
这是一个很大的文本,所以让我总结一下:
1.你拥有的物品,你需要处理掉。
1.你不要的东西,你不要丢弃。
还有第三种情况,听起来不像是你有,但尽管如此。
如果您在单一方法内,以本机方式建构、使用和舍弃对象,而不将它传递或储存在类别的字段中,则改用using陈述式,如下所示:

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

eanckbw96#

这样做当然是最佳实践,尤其是在处理繁重/非托管对象时。
编辑:最佳实践,但不是强制性的。

yc0p9oo0

yc0p9oo07#

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

klsxnrf1

klsxnrf18#

当然,如果您使用C++/CLI,您可以消除IDisposable的大量(重新)实现成本,并获得非常接近托管堆上对象的确定性终结的东西。这是一种语言中经常(我发现)被忽视的方面,许多人似乎将其置于“仅用于粘合代码”的位置。

eqfvzcg8

eqfvzcg89#

使用Dispose提供显式控制时,应使用Finalize方法提供隐式清除。Finalize提供备份,以防止在程序员未能调用Dispose时资源永久泄漏。
我认为实现这一点的最好方法是使用Dispose和Finalize方法的组合。您可以找到更多的Here

相关问题