我经常听说建议在使用完对象后将其设置为`Nothing`。但我通常只在表单内的函数中使用它们。
无论是否将对象设置为`Nothing`,当函数作用域结束时,引用都会丢失并释放内存,对吗?
也就是说,真的有必要这样做吗:
Set db = Nothing
Set record_set = Nothing
VB使用一种叫做“引用计数”的机制来确定一个对象是否可以被销毁。
当对象引用存储在变量中,例如使用Set
关键字时,该对象的引用计数会增加。当变量超出范围时,引用计数将减少。
当计数器达到零时,对象就可以被销毁了。对象资源将在此时释放。
函数局部变量很可能引用一个引用计数不会超过1的对象,因此当函数结束时,对象资源将被释放。
当您将对象传递给其他函数或存储在寿命更长的对象中时,引用计数将超过1。
将变量设置为Nothing
是显式减少引用计数的方法。
例如,您读取一个文件,并在file.ReadAll()
调用后立即将文件对象变量设置为Nothing
。文件句柄将立即释放,您可以花费时间处理内容。
如果您不设置为Nothing
,则文件句柄可能比绝对必要的时间开放更久。
如果你不是处于“必须解除有价值资源”的情况下,让变量超出作用域是可以的。
垃圾回收很少是完美的。即使在.NET中,有时您也强烈建议及早提示系统执行垃圾回收。
出于这个原因,我在完成记录集时明确地关闭并将其设置为无。
Close
并将所有内容设置为Nothing
的代码正确非常繁琐且容易出错。我敢打赌,你提到的代码只是在方法或循环结束时有这些行,而实际上当方法中先发生异常时,这些行甚至没有被调用。即使在方法中只有一个对象,这种情况也经常发生;当有10个对象,每个对象都应该在正确的时刻手动销毁,无论异常和执行流如何,它都变得完全不可管理。 - GSerg在Microsoft DAO帮助和Access开发人员参考手册中,"Recordset.Close"的帮助主题的最后一行是这样的:
"关闭方法的替代方法是将对象变量的值设置为Nothing (Set dbsTemp = Nothing)。"
http://msdn.microsoft.com/en-us/library/bb243098.aspx
考虑到这一点,微软知识库中的本篇文章“如何在使用数据访问对象(DAO)后防止数据库膨胀”告诉你,如果不想让数据库膨胀,就应该明确地关闭。你会注意到,该文章对细节有些含糊不清;“原因”部分几乎是无意义的。http://support.microsoft.com/kb/289562
症状:使用数据访问对象(DAO)打开记录集后,Microsoft Access数据库开始膨胀(或快速增长)。当变量超出范围时,引用应该被清理。随着软件的更新,这一点可能已经得到了改善,但曾经是不可靠的。我认为明确将变量设置为 "Nothing" 仍然是一个好习惯。
通常我会在我的过程的结尾处加上这个代码,或者在使用模块级别的记录集时调用一个名为“CloseRecordSet”的子程序:
Private Sub Rawr()
On Error GoTo ErrorHandler
'Procedural Code Here.
ExitPoint:
'Closes and Destroys RecordSet Objects.
If Not Recset Is Nothing Then
If Recset.State = 1 Then
Recset.Close
Conn.Close
End If
Set Recset = Nothing
Set Conn = Nothing
End If
Exit Sub
ErrorHandler:
'Error Handling / Reporting Here.
Resume ExitPoint
End Sub
Eric Lippert(长期担任微软编程语言设计师)在2004年就此问题写过文章。这篇文章提到了 VBScript、VBA 和 VB6 混合在一起使用,他的结论似乎适用于所有这些语言。
简而言之: 他得出以下结论:
以下是关键点的摘要。
首先,他引用了一些听起来合理但不正确的理由:
解释#1:(虚假)也许某个早期版本的VB需要这样做。
...
解释#2:(虚假)VB6垃圾回收器不清除循环引用。
两者都是不正确的假设。
接下来,对于为什么一些程序员可能过度采用这种做法的演绎推理:
解释#3:及早丢弃昂贵的资源是一个好主意。也许人们过度概括了这个规则?
...
我可以看出如何过度应用这个良好的设计原则会导致这种编程实践。... 尽管如此,我仍然不相信这就是整个故事。
在我看来,最有说服力的论点是最后一个,它引用了一个已知的ADO问题,实际上需要明确的对象清理:
解释 #4:……在变量超出范围之前自己清除变量和让作用域终结器为您清除变量之间存在区别。……
如果两个对象有一些复杂的交互,并且其中一个对象存在错误,必须在另一个对象之前关闭它,那么作用域终结器可能会选择错误的对象!
……
唯一解决错误的方法是在变量超出范围之前以正确的顺序显式清理对象。
事实上,有广泛使用的 ADO 对象存在这种错误。谜团得到了解决。
他接着提到,即使是微软的文档也可能鼓励使用 Set = Nothing 的做法,这影响了许多程序员,他们可能不知道这种做法的真正原始需求。
他也正确地批评了事情发展到了这个地步:
对我来说真正奇怪的是这种编码实践是如此顽固。好吧,有些对象存在缺陷,有时你可以通过编写一些本来不必要的代码来解决bug。逻辑上的结论是“总是编写不必要的代码,以防将来发生某些bug”吗?有些人称之为“防御性编程”,我称之为“大量过度概括”。试试这个
If Not IsEmpty(vMyVariant) Then
Erase vMyVariant
vMyVariant = Empty
End If
With
块)。因此,我将这个问题标记为重复,而不是另一种方式。 - GSerg