Watch out for computed properties

| | Comments (3)

They contain a gotcha which is very logic, and very easy to overlook if you're not careful. Most people tend to think of a computed property as the same thing as a regular property. And in many cases, this is acceptable to do. However, you need to watch out for reference counted return types!

Let's say you've got the following computed property:
[rbcode]
Property Foobar as Dictionary
Get
return new Dictionary
End Get

Set
End Set
End Property[/rbcode]
Looks rather innocuous, doesn't it? But it's not -- it's hiding a nast bug. Check this out:
[rbcode]
someClass.Foobar.Value( "testing" ) = "I am a bug"
[/rbcode]
Did anyone spot the bug?

The issue is that you're returning a new instance of a dictionary from the getter. This new instance has a reference count of 1 (because it was created). Then you're setting a new value on it for the key "testing" and "I am a bug" as the value. Then, the line of code finishes executing, and the reference count is decremented because nothing is holding that reference. Poof, your changes are gone!

This is a classic problem that you have to worry about (you could already run into this problem with regular methods, for instance): is it ByVal or ByRef? When something is passed ByVal, the assumption is that you've only got a value. So any changes you make to it are not going to be propagated anywhere. However, if something is passed ByRef, then you have an actual reference to the object itself, and your changes will be propagated.

In the above example, we essentially made a ByVal return type for an object! This could come in very handy in some instances, so it's not a bug that this happens. But it's certainly a language gotcha if you don't watch out for it.

So let's extend this into some imaginary syntax and see where else this could bite you. You cannot (currently) make a computed property out of an array. And, if you're paying close attention, you'll notice that arrays are reference types as well. So this same issue could crop up with an array.

Compare these two (legal) syntaxes and see what your results are:
[rbcode]
Function Foobar1() as Integer()
Dim arr(-1) as Integer = Array( 1, 2, 3 )
return arr
End Function

Dim mArray(-1) as Integer
Function Foobar2() as Integer()
return mArray
End Function

Foobar1.Append( 4 )
MsgBox Str( UBound( Foobar1 ) )

Foobar2.Append( 4 )
MsgBox Str( UBound( Foobar2 ) )
[/rbcode]
When you run this code, you'll see the first MsgBox pop up 2, which is "wrong" in that you appended another item, so the UBound should have been three (remember, UBound is the highest legal index in the array -- the array is zero-based). The second MsgBox displays 0, which is "correct" since you've added the first item to the array.

As you can see, the array requires persistent backing storage in order to retain information. You don't see this problem when you use straight value types such as an Integer since those store their information by value instead of by reference.

So that's your little "watch out for this" tip for the day. I should note that while I realize I've sounded negative about the behavior, it is the desirable behavior and it is a sensible design. It has its uses (such as creating a ByVal object return type)! I just don't want anyone to get caught not understanding the uses and how to get to them, that's all. :-)

3 Comments

Should returning the object work OK if you have a second (non-computed) property that the computed property references, and therefore exists as persistent storage?

Property mFoobar as Dictionary // Allocated nd initialized in object's constructor

Property Foobar as Dictionary
Get
return mFoobar
End Get

someClass.Foobar.Value( "testing" ) = "I am a bug"

Hey, look, Aaron -- I can't type any better than you! :)

Yup -- so long as you've got something backing the storage, then everything stays persistent.

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.