Strange collection class behavior with objects
Alan Stevens asks:
Doug Kimzey discovered an odd behavior in the VFP collection class today. It only occurs when the collection members are objects. Try running the following code, and see if you can make sense of it. For extra confusion, uncomment the second call to THIS.ADD() and try to explain why it works the first time. J
I’ve changed the original code to be more clear.
It creates a subclass of the Collection class and calls the NewItem method twice to add two items to the collection. When the code runs, the Final Count shows only one item.
It makes a lot of sense to me when I examine it under the debugger<g>
Try putting a breakpoint on the Destroy, then look up the callstack to see that the object is being destroyed on the “THIS.oCurrent = oo” line. Comment out that line, and you get a count of 2.
When an object’s reference count goes to 0, then the object is Destroyed.
If object A references object B, VFP internally tracks that A references B. If you delete A, the ref count for B is decremented.
When you set a variable or property to an object, then the ref count is incremented. Any prior value is released. (If the prior value is an object, the ref count is decremented.)
Thus the assignment statement “THIS.oCurrent = oo” will release any prior object reference contained by THIS.oCurrent. The 2nd time through, the first object’s reference is removed.
Removing an object reference from a Collection class means any reference between the object and the collection is removed. That means the element is removed from the collection.
A simple fix (below) is to make the collection a member of another class, rather than inheriting from Collection.
o = createobject("testcollection")
o.NEWITEM()
o.NEWITEM()
?"Final count",o.count
DEFINE CLASS testcollection AS COLLECTION
oCurrent = NULL
FUNCTION NEWITEM()
LOCAL oo as Object
THIS.ADD(NEWOBJECT("MyCustom"))
?"Newitem count is " + TRANSFORM(THIS.COUNT)
oo = THIS.ITEM(THIS.COUNT)
?"Post call to item count is " + TRANSFORM(THIS.COUNT)
THIS.oCurrent = oo
?"Post property assignment count is " + TRANSFORM(THIS.COUNT)
?
ENDDEFINE
DEFINE CLASS MyCustom AS Custom
PROCEDURE Destroy
?PROGRAM(),this.Name
ENDDEFINE
Simple fix:
o = createobject("testcollection")
o.NEWITEM()
o.NEWITEM()
?"Final count",o.oColl.count
DEFINE CLASS testcollection AS Custom
ADD OBJECT oColl as collection
oCurrent=NULL
FUNCTION NEWITEM()
LOCAL oo as Object
THIS.oColl.ADD(NEWOBJECT("MyCustom"))
?"Newitem count is " + TRANSFORM(THIS.oColl.COUNT)
oo = THIS.oColl.ITEM(THIS.oColl.COUNT)
?"Post call to item count is " + TRANSFORM(THIS.oColl.COUNT)
THIS.oCurrent = oo
?"Post property assignment count is " + TRANSFORM(THIS.oColl.COUNT)
?
ENDDEFINE
DEFINE CLASS MyCustom AS Custom
PROCEDURE Destroy
?PROGRAM(),this.Name
ENDDEFINE
Comments
- Anonymous
September 27, 2006
Where's the VB version <g,d,r> - Anonymous
September 27, 2006
Where's the VB version? <g,d,r> - Anonymous
September 27, 2006
I'm sorry, but your explanation is not complete :(
CH> When you set a variable or property to an object, then the ref count is incremented.
That's ok.
CH> Any prior value is released. (If the prior value is an object, the ref count is decremented.)
That's also understandable.
CH> Thus the assignment statement “THIS.oCurrent = oo” will release any prior object reference contained by THIS.oCurrent. The 2nd time through, the first object’s reference is removed.
AFAIK removing/releasing object reference is not the same as removing object itself! I can have 10 references (it does not matter whether they are variables or object properties) to some object and whatever I do then, I shan't lose object until ALL these references disappear (either by releasing variables, or by assigning new values to properties)...
CH> Removing an object reference from a Collection class means any reference between the object and the collection is removed. That means the element is removed from the collection.
I don't understand this part.
I suppose "Removing an object reference from a Collection" is done by Collection.Remove() method. And one more "special case" - object is destroyed by itself - for example Form.Release().
But WHY do collection kick out element in abovementioned case? I can add more references to this "disapearing" object (for example in PUBLIC variable, or as you show in second example - in another object property) - and it will result in object being persisted, but reference will be still kick out of this collection.
The only thought I have at the moment - Collection class treats ALL it's properties in a special manner - if property contain reference to object being inside this collection - then this reference (supposed to be "normal") is somehow replaced with "special" reference exists between collection itself and objects contained in it! So o.oCurrent in above example is not "real" reference - it is a kind of alias to o.Item(1) - with special rule - "remove o.Item(1) if o.oCurrent changed/reassigned"...
Other interesting side-effects:
1) Reverse of initial "problem"
o1 = CREATEOBJECT("Collection")
o1.Add(CREATEOBJECT("Custom"))
o1.AddProperty("oItIsSpecial")
o1.oItIsSpecial = o1.Item(1)
* Just a simple check
? ACLASS(la1, o1.oItIsSpecial)
DISPLAY MEMORY LIKE la1
o1.Remove(1)
* What is o1.oItIsSpecial now?
* It is a kind of "dead" object - working with it may lead to VFP crash
* (after some memory allocation/deallocation - for example other objects creation/releasing)
* or to mutation this "object" to an instance of "Empty" class - in best case :(
2) A bit more fun with RefCount :)
o1 = CREATEOBJECT("Collection")
o1.Add(CREATEOBJECT("Form"))
o1.AddProperty("oItIsSpecial")
o1.oItIsSpecial = o1.Item(1)
* What is RefCount for "Form" now?
* Suppose it is 2 (as we have 2 references - o1.oItIsSpecial and o1.Item(1))
* Make one more reference - now RefCount must be 3
o2 = o1.Item(1)
* Just to see when object will dissapear
o2.Show()
* Release one reference
o1.Remove(1)
* Now we have 2 references o1.oItIsSpecial and o2
o1.oItIsSpecial.Move(10,10)
o2.Caption = "It's me!"
* Release one more reference
o2 = .F. && Or RELEASE o2 - doesn't mater
* Surprise! Form dissapeared!
* We are wrong in above suggestions
* o1.oItIsSpecial and o1.Item(1) together made RefCount=1 not 2!
* BTW in this case o1.oItIsSpecial is still a kind of "Form" object - I'd say semi-empty object ;)
? o1.oItIsSpecial.Name
* After some manipulations it will become "Empty" class instance
* or maybe crash VFP :(
? o1.oItIsSpecial.Move(10,10) - Anonymous
October 01, 2006
Here is another sample that shows something similar to what IgorK demonstrated. Properties of the Collection are acting very peculiar. There seems to be more going on than a Reference count. Check out the 3rd example below.
CLEAR
?"Using Property:"
oColl=NEWOBJECT("Collection")
oColl.AddProperty("Current")
oColl.Add(NEWOBJECT("Custom"))
?oColl.Count && returns 1
oColl.Current = oColl.Item(oColl.Count)
?oColl.Count && returns 1
oColl.Current = NULL
?oColl.Count && returns 0
?"Using Variable:"
oColl=NEWOBJECT("Collection")
oColl.AddProperty("Current")
oColl.Add(NEWOBJECT("Custom"))
?oColl.Count && returns 1
oItem = oColl.Item(oColl.Count)
?oColl.Count && returns 1
oItem = NULL
?oColl.Count && returns 1
?"Using Variable and Property:"
oColl=NEWOBJECT("Collection")
oColl.AddProperty("Current")
oColl.Add(NEWOBJECT("Custom"))
?oColl.Count && returns 1
o2 = oColl.Item(oColl.Count)
oColl.Current = o2
?oColl.Count && returns 1
oColl.Current = NULL
?oColl.Count && returns 0
** We have lost our .Item, but the object still exists
?o2.Name && returns "Custom"
o2 = NULL
?oColl.Count && returns 0