By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
434,826 Members | 2,384 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 434,826 IT Pros & Developers. It's quick & easy.

Tip for dealing with circular reference issue

P: n/a
I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's about
the only down-side.

The issue...
You need an object model that includes a container object, and the object in
the container need to interact with the container itself. The problem is
that, to access the container, each item needs a reference to the container,
and that constitutes a circular reference since the container also contains an
array or a collection that contains a reference to the contained object.
Unless extreme caution is used, this structure can hold itself in memory even
though all externalr references to it have gone out of scope, and memory will
gradually be swallowed up each time a new one is created.

As of Access 2000, there is a tool that seems to handle this which is the
ability to create and raise custom events to communicate with a referring
object, but this only works for single references, not arrays or collections.

The solution...
Create a generic proxy class that can be referenced by both a container and a
contained item that can be asked to raise an event to the container to ask for
a temporary reference to the container for use within a single function call.

clsContainerAccessor
==========
Option Explicit
Option Compare Database

Event RequestReference(ByRef objReference As Object)

Public Property Get Reference() As Object
Dim objReference As Object
RaiseEvent RequestReference(objReference)
Set Reference = objReference
End Property
==========

In the contianer, add code like this...
==========
Private WithEvents mobjAccessor As clsContainerAccessor

Private Sub mobjAccessor_RequestReference(objReference As Object)
Set objReference = Me
End Sub

Private Sub Class_Initialize()
Set mobjAccessor = New clsContainerAccessor
End Sub
==========

When the container creates a new contained item, it can supply the item with a
reference to mobjAccessor, and the item can obtain a temporary reference to
the container by accessing the Reference property of that object as needed.
Nov 12 '05 #1
Share this Question
Share on Google+
11 Replies


P: n/a
rkc

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:6k********************************@4ax.com...
I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's about the only down-side.

The issue...
You need an object model that includes a container object, and the object in the container need to interact with the container itself. The problem is
that, to access the container, each item needs a reference to the container, and that constitutes a circular reference since the container also contains an array or a collection that contains a reference to the contained object.
Unless extreme caution is used, this structure can hold itself in memory even though all externalr references to it have gone out of scope, and memory will gradually be swallowed up each time a new one is created.


How do you suppose Me.Parent works without swallowing up memory?

Nov 12 '05 #2

P: n/a
It does not cache an instance -- there is a weak ref to the parent and the
object is retrieved as needed.
--
MichKa [MS]
NLS Collation/Locale/Keyboard Development
Globalization Infrastructure and Font Technologies

This posting is provided "AS IS" with
no warranties, and confers no rights.
"rkc" <rk*@yabba.dabba.do.rochester.rr.nope> wrote in message
news:Qt*******************@twister.nyroc.rr.com...

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:6k********************************@4ax.com...
I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's about
the only down-side.

The issue...
You need an object model that includes a container object, and the object in
the container need to interact with the container itself. The problem

is that, to access the container, each item needs a reference to the

container,
and that constitutes a circular reference since the container also

contains an
array or a collection that contains a reference to the contained object.
Unless extreme caution is used, this structure can hold itself in memory

even
though all externalr references to it have gone out of scope, and memory

will
gradually be swallowed up each time a new one is created.


How do you suppose Me.Parent works without swallowing up memory?

Nov 12 '05 #3

P: n/a
rkc

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:6k********************************@4ax.com...
I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's about the only down-side.


<snip>

Your post and M.Kaplan's reply to my me.parent question caused me to
spend my hung-over morning reading up on this.

This was the most interesting article I found:
http://www.programmersheaven.com/art...y/article1.htm

Also
http://msdn.microsoft.com/library/de...references.asp

and
http://msdn.microsoft.com/library/de...ctlifetime.asp

This is how I implemented your solution.
Do I get it?

<Employee class>
Private Name_ As String
Private Department_ As Accessor

Public Property Let Name(n As String)
Name_ = n
End Property

Public Property Get Name() As String
Name = Name_
End Property

Public Property Set Department(acc As Accessor)
Set Department_ = acc
End Property

Public Property Get Department() As String
Department = Department_.Reference.Name
End Property
</Employee class>

<Department class>
Public Name As String
Public Employees As New Collection
Private WithEvents mobjAccessor As Accessor

Public Sub AddEmployee(ByVal strName As String)
Dim e As Employee

Set e = New Employee
e.Name = strName
Set e.Department = mobjAccessor
Employees.Add e

Set e = Nothing
End Sub

Private Sub mobjAccessor_RequestReference(objReference As Object)
Set objReference = Me
End Sub

Private Sub Class_Initialize()
Set mobjAccessor = New Accessor
End Sub
</Department class>
Nov 12 '05 #4

P: n/a
It's not the style I would have used, but yes, you did it right, and you
appear to get it.

The main thing I would do differently is to never use underscore characters in
the names of anything in VB/VBA (except where it puts them, such as
mobjAccessor_RequestReference). VB has its own meanings for underscores, and
you can mess yourself up if you use them yourself.

Specifically, Access uses underscores to identify event handlers and
implementations of interface members.

On Thu, 01 Jan 2004 17:37:24 GMT, "rkc" <rk*@yabba.dabba.do.rochester.rr.nope>
wrote:

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:6k********************************@4ax.com.. .
I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's

about
the only down-side.


<snip>

Your post and M.Kaplan's reply to my me.parent question caused me to
spend my hung-over morning reading up on this.

This was the most interesting article I found:
http://www.programmersheaven.com/art...y/article1.htm

Also
http://msdn.microsoft.com/library/de...references.asp

and
http://msdn.microsoft.com/library/de...ctlifetime.asp

This is how I implemented your solution.
Do I get it?

<Employee class>
Private Name_ As String
Private Department_ As Accessor

Public Property Let Name(n As String)
Name_ = n
End Property

Public Property Get Name() As String
Name = Name_
End Property

Public Property Set Department(acc As Accessor)
Set Department_ = acc
End Property

Public Property Get Department() As String
Department = Department_.Reference.Name
End Property
</Employee class>

<Department class>
Public Name As String
Public Employees As New Collection
Private WithEvents mobjAccessor As Accessor

Public Sub AddEmployee(ByVal strName As String)
Dim e As Employee

Set e = New Employee
e.Name = strName
Set e.Department = mobjAccessor
Employees.Add e

Set e = Nothing
End Sub

Private Sub mobjAccessor_RequestReference(objReference As Object)
Set objReference = Me
End Sub

Private Sub Class_Initialize()
Set mobjAccessor = New Accessor
End Sub
</Department class>


Nov 12 '05 #5

P: n/a
rkc

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:gn********************************@4ax.com...
It's not the style I would have used, but yes, you did it right, and you
appear to get it.

The main thing I would do differently is to never use underscore characters in the names of anything in VB/VBA (except where it puts them, such as
mobjAccessor_RequestReference).


My naming conventions in throw away code change with the wind direction.
That's the first time I have ever used and underscore to indicate a member
variable. Just wanted to see how it looked.

I'll take your advice on it.


Nov 12 '05 #6

P: n/a
On Thu, 01 Jan 2004 23:30:01 GMT, "rkc" <rk*@yabba.dabba.do.rochester.rr.nope>
wrote:

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:gn********************************@4ax.com.. .
It's not the style I would have used, but yes, you did it right, and you
appear to get it.

The main thing I would do differently is to never use underscore

characters in
the names of anything in VB/VBA (except where it puts them, such as
mobjAccessor_RequestReference).


My naming conventions in throw away code change with the wind direction.
That's the first time I have ever used and underscore to indicate a member
variable. Just wanted to see how it looked.

I'll take your advice on it.


Incidenally, if anyone is interested, there is also a more complicated
solution that can work in Access 97 because it doesn't use custom events. I
prefer it to pointer-based weak references because it doesn' have to "look
under the hood" to work.

Basically, when you have a structure of interrelated objects that must "see"
each other, you wrap the whole structure in another object reference that can
properly tear down the structure it wraps when it gets terminated itself.

Here's a simplified example:

clsDepartment
==========
Option Compare Database
Option Explicit

Public Department As clsDepartmentData
Public Employees As VBA.Collection

Private Sub Class_Initialize()
Set Department = New clsDepartmentData
Set Employees = New VBA.Collection
Department.Setup Employees
End Sub

Private Sub Class_Terminate()
While Employees.Count > 0
Employees.Remove 1
Wend
End Sub

Public Function AddEmployee() As clsEmployeeData
Dim objNewEmployee As clsEmployeeData
Set objNewEmployee = New clsEmployeeData
objNewEmployee.Setup Department
Employees.Add objNewEmployee
Set AddEmployee = objNewEmployee
End Function
clsDepartmentData
==========
Option Compare Database
Option Explicit

Private mcolEmployees As VBA.Collection

Public Sub Setup(colEmployees As VBA.Collection)
Set mcolEmployees = colEmployees
End Sub
clsEmployee
==========
Option Compare Database
Option Explicit

Private mobjDepartment As clsDepartmentData

Public Sub Setup(objDepartment As clsDepartmentData)
Set mobjDepartment = objDepartment
End Sub
The circular reference occurs because the clsDepartmentData object refers to a
Collection object which refers to 0 or more clsEmployeeData objects which each
refer to back to the clsDepartmentData object. The trick that allows for
proper cleanup is that nothing in that reference loop references the
clsDepartment object, so that will be allowed to terminate properly, and when
it terminates, it removes all the clsEmployeeData entries from the collection,
so they are no longer referenced from anything and will then go out of scope.
Now, with the clsEmployeeData objects gone, only the clsDepartment object
refers to the clsDepartmentData object, so it, too can go out of scope along
with the clsDepartment object.

The down-side of this approach is that, when structures become more
complicated than a simple 1-M, the logic can get hairy really fast, and that
means it can be easy to overlook a circular reference case that will not be
torn down properly. On the plus-side, this code may perform better than the
Event proxy approach in many cases (not all cases as it might appear, but many
cases).
Nov 12 '05 #7

P: n/a
rkc

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:ic********************************@4ax.com...
Incidenally, if anyone is interested, there is also a more complicated
solution that can work in Access 97 because it doesn't use custom events. I prefer it to pointer-based weak references because it doesn' have to "look
under the hood" to work.


There's also keeping a collection of Container objects and simply
storing the key as a member of the Contained object.
Nov 12 '05 #8

P: n/a
On Sat, 03 Jan 2004 00:54:59 GMT, "rkc" <rk*@yabba.dabba.do.rochester.rr.nope>
wrote:

"Steve Jorgensen" <no****@nospam.nospam> wrote in message
news:ic********************************@4ax.com.. .
Incidenally, if anyone is interested, there is also a more complicated
solution that can work in Access 97 because it doesn't use custom events.

I
prefer it to pointer-based weak references because it doesn' have to "look
under the hood" to work.


There's also keeping a collection of Container objects and simply
storing the key as a member of the Contained object.


Yes, I've used that one, but it proves to be pretty hairy, too. Basically,
you have a global Collection variable, and dynamically generate unique keys
for the items in it. Objects in relationship keep collections of the keys of
their relatives, and look them up dynamically when needed.

I decided this seemed unattractive because you need another standard module
that does nothing more than hold the global collection, and you also need an
extra step to generate a session-wide unique key for each item in the
collection. The best I was able to come up with is concatenating a looping
counter value to the address of the object (ObjPtr). The ObjPtr alone is not
enough because it does nto distinguish between an item no longer held in the
colleciton and a new one that happens to have been allocated in the same
memory location. Technically, the cylcing pointer doesn't totally solve the
problem either, but the odds of a duplicate are astronomically small.
Nov 12 '05 #9

P: n/a
tom
Steve,

This all looks very sexy, and I'm eager to try it out. Is it all
still holding up as far as you are concerned? How did you determine
that the objects are terminating properly?

I realize that there may be a circular reference or two in the
behemoth we have here. It would be very good to use this to clean that
up.

Thanks,
-Tom

I just came up with a really tidy little solution to the VB/VBA circular
reference issue. It only works with Access 2000 or newer, but that's about
the only down-side.

Nov 12 '05 #10

P: n/a
On 9 Jan 2004 21:26:37 -0800, to*@nuws.com (tom) wrote:
Steve,

This all looks very sexy, and I'm eager to try it out. Is it all
still holding up as far as you are concerned? How did you determine
that the objects are terminating properly?


Any time there is a place where you know a serious bug could creep into your
code, it's best to have a test for that condition. In my code, I have a
defined constant indicating that the code is running in debug mode, and if
statements in the code that check that constant to see if they should run
debugging code.

For the code in question, I created a global counter that is incremented each
time one of these classes is instantiated, and decremented each time one is
terminated. When all my parent objects have gone out of scope, the counter
should hold a value of zero. If the counter ends up zero, everything's fine,
otherwise, there's a leak.

Nov 12 '05 #11

P: n/a
Steve Jorgensen <no****@nospam.nospam> wrote:
This all looks very sexy, and I'm eager to try it out. Is it all
still holding up as far as you are concerned? How did you determine
that the objects are terminating properly?


Any time there is a place where you know a serious bug could creep into your
code, it's best to have a test for that condition. In my code, I have a
defined constant indicating that the code is running in debug mode, and if
statements in the code that check that constant to see if they should run
debugging code.

For the code in question, I created a global counter that is incremented each
time one of these classes is instantiated, and decremented each time one is
terminated. When all my parent objects have gone out of scope, the counter
should hold a value of zero. If the counter ends up zero, everything's fine,
otherwise, there's a leak.


Ah, nice idea. Thanks for the posting.

Tony
--
Tony Toews, Microsoft Access MVP
Please respond only in the newsgroups so that others can
read the entire thread of messages.
Microsoft Access Links, Hints, Tips & Accounting Systems at
http://www.granite.ab.ca/accsmstr.htm
Nov 12 '05 #12

This discussion thread is closed

Replies have been disabled for this discussion.