Partager via


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