HiveBrain v1.2.0
Get Started
← Back to all entries
patternModerate

Nullable<T> Implementation for VB6/VBA

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
vb6nullableforvbaimplementation

Problem

Because I was spoiled with C# and the .NET framework, whenever I have to work with VB6 I feel like something's missing in the language. A little while ago I implemented a List for VB6 (here), and before that I implemented String.Format() and a number of string-helper functions (here). Don't go looking for a StringFormat method in the VB6 language specs, that method is the one I've written.

Today I would have liked to be able to declare a Nullable in VB6, so I implemented a class that allowed me to do that. I named this class Nullable and it goes like this:

Private Type tNullable
    Value As Variant
    IsNull As Boolean
    TItem As String
End Type

Private this As tNullable
Option Explicit

Private Sub Class_Initialize()
    this.IsNull = True
End Sub


Now before I go any further I have to mention that I have used "procedure attributes" in the Value property, making it the type's default member:

Public Property Get Value() As Variant
'default member
    Value = this.Value
End Property

Public Property Let Value(val As Variant) 'damn case-insensitivity...
'default member
    If ValidateItemType(val) Then
        this.Value = val
        this.IsNull = False
    End If
End Property

Public Property Set Value(val As Variant)
'used for assigning Nothing.
'Must be explicitly specified (e.g. Set MyNullable.Value = Nothing; Set MyNullable = Nothing will not call this setter)
    Dim emptyValue As Variant
    
    If val Is Nothing Then
        this.IsNull = True
        this.Value = emptyValue
    Else
        Err.Raise vbObjectError + 911, "Nullable", "Invalid argument."
    End If
    
End Property


The ValidateItemType private method determines whether the type of a value is "ok" to be assigned as the instance's Value:

```
Private Function ValidateItemType(val As Variant) As Boolean
Dim result As Boolean

If Not IsObject(val) Then
If this.TItem = vbNullString Then this.TItem = TypeName(val)
result = IsType

Solution

I think the itself class might be mis-named, because it is really 'Empty-able' not Nullable or 'Nothing-able'.

You have to keep in mind that Empty, Null, and Nothing are very different concepts in VB6. Setting and object to Nothing is basically just syntactic sugar for releasing the pointer to the Object. This is the same as asking for ObjPtr() to return Null for that instance (although there is no way to test this in VB6 - see the code and explanation below).

Null is actually better to conceptualize in VB6 as a type rather than an uninitialized variable, as the code below demonstrates:

Dim temp As Variant

'This will return "True"
Debug.Print (temp = Empty)

'This will return "False"
Debug.Print (IsNull(temp))

temp = Null
'This will return "True"
Debug.Print (IsNull(temp))

'This will return "Null"
Debug.Print (TypeName(temp))


This brings me to the explanation of why your class should really be referred to as 'Empty-able'. A Variant is best thought of as an object with 2 properties - a type and a pointer. If it is uninitialized, it basically has a pointer to Nothing and a type of Empty. But is isn't Null, because the Variant itself still exists with its default "properties".


However if I do Set n = Nothing, not only Debug.Print n Is Nothing
will print False, the instance gets reset to a Nullable and
...the setter (Public Property Set Value) does not get called

This is because of VB6's obnoxious default behavior when you use a reference to an object that was set to nothing. It "helpfully" creates a new object for you as can be verified by the code below - before the second call to ObjPtr(temp), it implicitly runs Set temp = New Test. You should be able to verify this with a Debug.Print in Class_Initialize().

Private Sub Testing()

    Dim temp As New Test

    Debug.Print (ObjPtr(temp))

    Set temp = Nothing

    'The code below instantiates a new Test object, because it is used after being released.
    Debug.Print (ObjPtr(temp))

End Sub


VB6 treats setting an Object equal to Nothing as a special case, so it never calls the Property Set. What is it basically doing is: AddressOf(n) = AddressOf(Nothing).

EDIT:
Excellent explanation of how Variants work under the hood here.

Code Snippets

Dim temp As Variant

'This will return "True"
Debug.Print (temp = Empty)

'This will return "False"
Debug.Print (IsNull(temp))

temp = Null
'This will return "True"
Debug.Print (IsNull(temp))

'This will return "Null"
Debug.Print (TypeName(temp))
Private Sub Testing()

    Dim temp As New Test

    Debug.Print (ObjPtr(temp))

    Set temp = Nothing

    'The code below instantiates a new Test object, because it is used after being released.
    Debug.Print (ObjPtr(temp))

End Sub

Context

StackExchange Code Review Q#41601, answer score: 10

Revisions (0)

No revisions yet.