关闭和释放资源 - 应该调用哪个方法?

188
8个回答

231

我想澄清这种情况。

根据微软的指南,如果合适的话提供Close方法是一个好的实践。这里是来自Framework design guidelines的引用

考虑在Dispose()之外提供Close()方法,如果关闭是该领域的标准术语。在这样做时,重要的是您使Close实现与Dispose相同...

在大多数情况下,CloseDispose方法是等效的。在SqlConnectionObject的情况下,CloseDispose主要区别是:

应用程序可以调用Close多次。不会生成异常。

如果您调用了Dispose方法,则SqlConnection对象状态将被重置。如果您尝试在已释放的SqlConnection对象上调用任何方法,您将收到异常。

也就是说:

  • 如果您只使用连接对象一次,请使用Dispose。使用using块可以确保即使出现异常也会调用该方法。
  • 如果必须重复使用连接对象,请使用Close方法。

6
@Chris,Close()的文档说明为“释放连接到连接池或关闭连接(如果禁用连接池)”。因此,使用Close()应该足以防止连接池溢出。 - David Hammond
4
Dispose() 方法是否会将连接释放回连接池? - oscilatingcretin
1
这是我在过去十年中阅读的关于这个问题最好的论点之一。非常好的观点。 - Michael Erickson
2
这是这样工作的: 1. con.Open() con.Close(); 2. con.Open(); // 重用 3. con.Dispose(); // 仅使用一次 con.Open(); // 错误 - Shaiju T

33

通常的答案是:这要视情况而定。不同的类以不同的方式实现IDisposable接口,需要您做必要的研究。

就SqlClient而言,推荐的做法如下:

using (SqlConnection conn = /* Create new instance using your favorite method */)
{
    conn.Open();
    using (SqlCommand command = /* Create new instance using your favorite method */)
    {
        // Do work
    }
    conn.Close(); // Optional
}

在连接上,您应该调用Dispose(或Close*)!不要等待垃圾回收器清理连接,否则这将占用池中的连接直到下一个GC周期(至少)。如果您调用Dispose,则无需调用Close,并且由于using结构使处理Dispose变得如此容易,因此实际上没有理由调用Close

连接会自动汇集,并且在连接上调用Dispose/Close不会在正常情况下物理关闭连接。不要尝试实现自己的汇集。 SqlClient 在从池中检索连接时执行清理操作(例如还原数据库上下文和连接选项)。

*如果您正在调用Close,请确保以异常安全的方式执行它(即在catch或finally块中)。


当你说“由你来进行必要的研究”时,指的是什么研究?我唯一确定的方法是通过反射,但这在大多数情况下都有违法的风险。 - Storm
12
我不会说: conn.Close(); // 可选的 这不是可选的。这是多余和不必要的。你正在两次处理该对象,这将被某些代码分析工具标记为警告。 - Metalogic
1
@Metalogic 我同意使用正确的using用法调用Close是多余和不必要的(而且很丑)。然而,吹毛求疵地说:调用Close并不等同于“disposing”(而Dispose则意味着对于SqlConnection来说Close是必须的)。相比之下,在using (var x = ..) { x.Dispose(); }中,此时的x确实被“disposed twice”。 - user2864740

13

SqlConnection 来说,从连接本身的角度来看,它们是等效的。根据 Reflector,Dispose() 方法会调用 Close() 并执行一些其他的内存释放操作,主要是通过将成员设置为 null 来实现。

而对于 Stream 来说,它们确实是等效的。 Stream.Dispose() 简单地调用了 Close() 方法。


1
你确定吗?MSDN说它是从Component继承的,这个类似乎没有做任何尝试调用Close()的操作。我在DBConnectionSqlConnection中找不到任何与这些通知相关的地方。但是,它确实有一个私有的DisposeMe()方法,在任何地方都没有被引用 - Deanna
@Deanna 这里被覆盖了:https://github.com/dotnet/corefx/blob/a10890f4ffe0fadf090c922578ba0e606ebdd16c/src/System.Data.SqlClient/src/System/Data/SqlClient/SqlConnectionHelper.cs#L144 - David Cumps
@DavidCumps,看起来自我留言4年以来已经改变了。我的链接已经失效了。 - Deanna
https://github.com/microsoft/referencesource/blob/master/System.Data/System/Data/SqlClient/SqlConnection.cs#L2585,我在这里没有看到它。 - Royi Namir

11

你确实需要调用Dispose()方法!

Dispose()方法是开发者需要主动调用的,垃圾回收器会自动调用Finalize()方法。如果你没有在对象上调用Dispose()方法,那么它们使用的任何非托管资源都不会被释放,直到垃圾回收器遍历并在它们上调用Finalize()方法(而且谁知道什么时候会发生这种情况)。

这种情况被称为不确定性终结,并且是.NET开发者常见的陷阱。如果你正在使用实现IDisposable接口的对象,请务必调用Dispose()方法!

http://www.ondotnet.com/pub/a/oreilly/dotnet/news/programmingCsharp_0801.html?page=last

虽然有很多情况(例如在SqlConnection上)你可以调用Disponse()方法来关闭连接或者关闭文件句柄等,但是除非你计划在不久的将来重新使用该对象,否则调用Dispose()方法几乎总是最好的选择!


30
这个评论完全是错误的。垃圾回收器从来不会调用“Dispose”。 - Stephen Cleary
3
推论:如果你没有使用 using() 语句来调用实现了 IDisposable 接口的类,那么你 应该 调用 Dispose() 方法。如果要调用的类实现了 IDisposable 并且你已经在页面上使用 using() 来包装它的使用,则可以使用 Dispose() 进行释放(别射我,这是双关语)。然而,对于任何显式使用 Open() 的内容,建议使用 Close() 方法。 - René Kåbis
我不确定其他数据库管理系统,但在 PostgreSql 中你不能同时执行这两个操作。一旦你 Close 了一个连接,Postgres 会自动将连接标识符设置为 null。从那时起,已经设置为 null 的 SQL 连接标识符就无法被 Dispose - ssd
这可能适用于IDbConnection的PostgreSql实现。然而,大多数实现(例如SqlConnection)在Dispose()方法内检查null/有效状态,以便状态更改方法永远不会针对空对象调用。 - Erik Midtskogen

6
< p > 这个原本只是一个简短的建议,结果变成了一个长答案。很抱歉。

正如泰勒在他很好的回答中指出的那样,调用Dispose()是一个很好的编程实践。这是因为这个方法被认为是“集合”所有需要释放资源的方法,以便没有不必要的开放资源。例如,如果您将一些文本写入文件,但未关闭文件(释放资源),则文件将保持打开状态,直到GC过来并执行您应该执行的操作。

现在,在某些情况下,会有更具体于您处理的类的“终结”方法,例如StreamWriter.Close(),它覆盖TextWriter.Close()。实际上,它们通常更适合情况:例如,StreamWriter的Close()Dispose()对象之前刷新流和底层编码器!酷!

然而,浏览MSDN时,您会发现即使是微软也有时会被众多的关闭器和处理器所困扰。例如,在此网页中,有些示例在隐式Dispose()之前调用Close()(如果您不了解为什么它是隐式的,请参见using语句),而在其中一个示例中,他们并没有费心这样做。那为什么呢?我也感到困惑。

我认为(强调一下,这是原创研究,如果我错了可能会损失声誉)的原因是Close()可能失败并抛出异常,同时保留资源,而Dispose()则肯定会释放它们。这就是为什么Dispose()应该始终保护Close()调用的原因(对双关语表达歉意)。
MyResource r = new MyResource();

try {
  r.Write(new Whatever());

  r.Close()
finally {
  r.Dispose();
}

是的,我想微软在那个例子中犯了一个错误。也许那个时间戳永远不会被刷新到文件中。

明天我会修复我的旧代码。

编辑:对不起Brannon,我不能评论你的答案,但你确定在finally块上调用Close()是一个好主意吗?我猜那可能会导致整个块出现异常,而这很可能包含重要的清理代码。

回复Brannon的:太好了,只是在真正需要时(例如处理流时-不太了解.NET中的SQL连接)不要忘记调用Close()


实际上,我从不调用Close(),我只是让Dispose()和'using'结构_做正确的事情_。如果您没有调用Dispose,则需要以异常安全的方式调用Close。在finally块中添加异常处理可能是一个好主意。 - Brannon
我的评论是针对SqlClient的。关键是,你需要了解你正在使用的类。总是调用Dispose并不一定是正确的答案。 - Brannon

2

将类型转换为iDisposable,并在其上调用dispose方法。这将调用配置为实现"iDisposable.Dispose"的任何方法,而不管函数命名如何。


该函数被命名为“Dispose”:所以我们回到了最初的问题:} - user2864740
该函数绑定到 IDisposable.Dispose,但这并不意味着这就是它的名称。请注意,在vb.net中,一个函数可以绑定到多个接口成员,这些成员的名称与该函数的名称无需相关。 - supercat
像这样进行强制类型转换:using (myObj as IDisposable) - Yousha Aleayoub

1
通常我们在Close(),Abort()和Dispose()中遇到问题,但是让我告诉您它们之间的区别。
1)ABORT:- 我不建议使用此选项,因为调用abort时,客户端将在不告知服务器的情况下删除连接,因此服务器将等待一段时间(大约1分钟)。如果您有大量请求,则无法使用abort(),因为它可能会导致有限连接池超时。
2)关闭:- 关闭连接的方法非常好,因为在关闭连接时,它会呼叫服务器并确认服务器也要关闭。
这里还有一件事要注意。在某些情况下,如果发生错误,则不能在finally中编写connection.close()代码,因为此时通信状态将出现故障。
3)处理:- 它是一种关闭方式,但是在关闭连接后,您无法再次打开它。
所以尝试这种方式,
private void CloseConnection(Client client)
    {
        if (client != null && client.State == CommunicationState.Opened)
        {
            client.Close();
        }
        else
        {
            client.Abort();
        }
    }

client != null 的检查是不正确/误导的,因为它不能保护所有用法。此外,我不确定代码如何达到“此连接未打开应关闭”的状态。 - user2864740

0

我在使用TransactionScope时遇到了问题:我连续创建、打开和释放了多个连接,但在完成范围时捕获到了此平台不支持分布式事务的错误(明显没有分布式事务)。

通过添加Close方法调用解决了这个问题。大多数答案都说DisposeClose是相同的,但它们似乎并不相同:从源代码中我得出的结论是,Close方法明确地将连接返回到池中,而我找不到与Dispose相同的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接