Function GetConfigDoc(key As String) As NotesDocument Dim s As New NotesSession 'Get the db containing the config documents Dim server As String, path As String server="SERVER" path="abc.nsf" Dim db As NotesDatabase Set db=s.GetDatabase(server, path) Set GetConfigDoc=db.Getview("vwConfig").Getdocumentbykey(key, True) End Function Sub Initialize Dim doc As NotesDocument Set doc=GetConfigDoc("No") Print doc.Values(0) End Sub例子2
Dim entry As NotesViewEntry Set entry=nav.Getchild(personEntry) 'nav is a NotesViewNavigator Dim i As Integer For i=1 To entry.Siblingcount-1 entry.Document.Type="Arrival" Call entry.Document.Save(True, False) Set entry=nav.Getnextsibling(entry) Next
诸君目测一下上述两段代码运行结果如何?
实际结果是例子1中的代码会报错Object variablenot set。例子2中的代码运行后完全没作用。如果在工作中遇到了这样的情况,你可能会抓破脑袋,最后只能悻悻地换一种写法。语法没有任何错误,更让人懊恼的是在调试状态下逐行运行也看不出什么异常。原因就出在LotusScript的垃圾回收。
Set view = db.GetView("$all") Set doc = view.GetFirstDocument() While Not (doc Is Nothing) ' do something... Set doc = view.GetNextDocument(doc) Wend
这是我们遇到过无数次的片段,看上去很简单。即使有50,000条文档,程序也能运行得很稳定,占用的资源不会比50条文档的时候多。我们来仔细看看,在while的循环体中,doc变量被不断指向新的文档对象,旧的文档对象哪里去了?这不正是自动垃圾回收应用的场合。LotusScript解释器自动销毁了这些对象,对程序员来说完全是透明的。一切都很好,对吧?那为什么例子1和例子2的结果会不正常呢?这是因为LotusScript的垃圾回收有以下几个特点:
1.每一行代码运行完之后都会进行,以检查是否有对象不再有变量引用。函数退出时则回收所有本地变量指向的对象。
2.某个对象只要不再有局部变量直接指向它,就会被回收,即使有其他对象通过属性关联到它。
3.回收一个对象时,它包含的其他对象都会被回收。包含指的是一个对象由另一个对象的方法或属性获得,比如从一个数据库对象获取某个视图,从视图取得文档,从文档得到域对象。因为Notes对象的“树”状关系,当一个数据库对象被销毁时,属于它的视图、文档等各种对象都会被回收。而当一个NotesSession被回收时,此次会话涉及的所有对象也都会被销毁。
可以看出这样的垃圾回收机制简单有效,但是做得过分了,特别是第一点和第三点同时发挥作用。现在我们就可以解释例子1和例子2的实际运行结果。在例子1里,函数GetConfigDoc返回一个文档对象,但是随即包含这个文档的数据库对象就被回收,根据上面的第三点特性,该文档对象也被回收,因而文档变量无法被访问。在例子2里,NotesViewEntry指向的文档对象修改这一行执行完之后,垃圾回收运行,因为没有变量指向,在下一行调用保存之前,该文档对象就被销毁了。调用保存方法时,文档对象是重新从NotesViewEntry获得的。
在实际编程中,我们还有可能不小心掉进这个过分强大的垃圾回收的陷阱里。幸好,了解了它的特点后,很容易就可以避开,只要稍微改动一下代码。对于例子1的情况,我们可以直接用函数返回配置文档中的字段值比如字符串和数字,或者在调用它的函数中,先创建一个数据库变量指向配置数据库。在例子2里,我们不能省去声明一个文档变量的步骤,这样因为有变量引用,文档对象就不会被回收:
Dim doc As NotesDocument Dim entry As NotesViewEntry 'a record entry Set entry=nav.Getchild(personEntry) 'the first record Dim i As Integer For i=1 To entry.Siblingcount-1 Set doc=entry.Document doc.Type="Arrival" Call doc.Save(True, False) Set entry=nav.Getnextsibling(entry) Next
最后值得提出的是,上述三条垃圾回收的原则执行时有一个例外,那就是当前的NotesSession和当前数据库的NotesDatabase对象不会在程序运行中被销毁,比如上面的GetConfigDoc函数如果获取的是当前数据库的配置文档,那么函数退出时数据库对象不会被回收,配置文档对象也能够成功返回。这两种对象直到当前LotusScript程序退出时,如单机一个按钮触发的代码执行完毕,或者一个代理运行结束,才会被销毁。这个例外是合理也可以解释的,在一个LotusScript程序退出前,表示当前会话的NotesSession和当前数据库的NotesDatabase的资源一直必然有效,不应该也不可能被回收。