循环引用的处理

通过分层结构中的包含关系可以从一个高层对象跟踪到它所包含的任何对象。

严格遵守包含关系的对象模型类似于树结构。任何给定的分枝(对象)都可以分为更小的分枝(从属对象),但是这些小分枝不能构成环路,也不能再次与树干或下一级的分枝相连。

如果一个从属对象的属性或变量拥有对包含其本身的对象的引用,就会导致带环的对象模型,或者叫做循环引用

例如,Order 对象有一个包含 Contact 对象引用的 Contact 属性,该 Contact 对象代表放置次序的个体。而 Contact 对象又有一个包含 Company 对象引用的 Company 属性。

到现在为止,这个分层结构还是一棵树。但是,如果 Company 对象有一个包含 Order 对象引用的 MostRecentOrder 属性,就产生了循环引用。

注意   要避免这种情况,把 MostRecentOrder 属性设为文本关键字,从部件的 Orders 集合中检索 Order 对象。

Visual Basic 部件中的循环引用

考虑最简单的循环引用方式:Parent 属性。从属对象的 Parent 属性具有对包含该对象的对象引用。

例如,Microsoft Excel 的对象模型,Button 对象被 Worksheet 对象包含。如果有对该 Button 对象的引用,使用下面的代码就可以打印出包含它的 Worksheet 的名称。

'如果变量 btnCurrent 包含对 Microsoft Excel Button 对象的引用,下面的代码行显示
'包含该 button  Worksheet 对象的 Name 属性。
MsgBox btnCurrent.Parent.Name

Microsoft Excel 是用 C++ 和低级 COM 接口编写的,它保持其对象的 Parent 属性且没有造成循环引用。如果要用 Visual Basic 来实现这种关系,就得考察 Visual Basic 处理创建和毁坏对象的方式。

当某个对象不再被引用时,Visual Basic 就毁坏该对象。如果某对象的 Parent 属性有一个包含该对象引用的集合,就足以阻止该对象被毁坏。同样地,如果 Parent 属性有一个包含该对象引用的对象属性,该对象也不会被毁坏。

当父对象被毁坏时,实现其属性的变量不再有效,对象引用被释放。这样就可以终止该从属对象。不过,如果从属对象有 Parent 属性,Visual Basic 不会毁坏第一个位置的父对象,因为这个从属对象还在引用它。

从属对象也不能被毁坏,因为父对象也有其引用。在图 6.7 中用进程外部件说明了这种情形。

图 6.7   循环引用阻止对象被毁坏。

客户端应用程序 B 已经释放了对其 Widget 对象的引用。但该 Widget 对象有一个对 Knob 对象的引用,而 Knob 对象的 Parent 属性又指回该 Widget 对象,使得二者均不能被终止。

如果 Widget 对象有一个 Knob 对象的集合,而不是单个的 Knob 对象,也会出现类似的问题。该 Widget 对象拥有对 Knobs 集合对象的引用,而该集合对象又拥有对每个 Knob 对象的引用。每个 Knob 对象的 Parent 属性又指回该 Widget,形成了一个使得 Widget 对象、Knobs 集合和 Knob 对象都能存活的循环。

客户端 B 所使用的对象要到关闭该部件时才会被毁坏。例如,如果客户端 A 释放了它的 Widget 对象,这样不存在对该部件的外部引用了。如果该部件没有装入任何窗体,且任何过程都没有在执行其代码,则该部件被卸载,此时会执行所有对象的 Terminate 事件。不过,在此之前的时间内会存在大量孤立的对象,占据内存。

注意   如果在两个进程外部件的对象之间存在循环引用,这些部件永远都不会终止。

循环引用与进程内部件

如果把部件做成 DLL 实现,以便在客户端应用程序的进程中运行,那么避免循环引用就更加重要。因为进程内部件与客户端应用程序共享进程空间,对公共对象的引用不存在‘外部’和‘内部’之分。只要有一个对部件所提供的对象的引用,该部件就不会被卸载。

这就意味着循环引用将使进程内部件无限期地装载,而由孤立状态的对象所占据的内存直到客户端应用程序关闭才能重新分配。

详细信息   关于对象模型的更详细的信息,请参阅“ActiveX 部件的标准及指南”。