# Nullable<T> Implementation for VB6/VBA

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<T> 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<bool> 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<T>", "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 = IsTypeSafe(val)
If Not result Then Err.Raise vbObjectError + 911, "Nullable<T>", StringFormat("Type mismatch. Expected '{0}', '{1}' was supplied.", this.TItem, TypeName(val))
Else
Err.Raise vbObjectError + 911, "Nullable<T>", "Value type required. T cannot be an object."
result = False
End If

ValidateItemType = result
End Function

Private Function IsTypeSafe(val As Variant) As Boolean
IsTypeSafe = this.TItem = vbNullString Or this.TItem = TypeName(val)
End Function


That mechanism is borrowed from the List<T> implementation I wrote before, and proved to be working fine. Shortly put, an instance of the Nullable class is a Nullable<Variant> until it's assigned a value - if that value is a Integer then the instance becomes a Nullable<Integer> and remains of that type - so the Value can only be assigned an Integer. The mechanism can be refined as shown here, to be more flexible (i.e. more VB-like), but for now I only wanted something that works.

The remaining members are HasValue() and ToString():

Public Property Get HasValue() As Boolean
HasValue = Not this.IsNull
End Property

Public Function ToString() As String
ToString = StringFormat("Nullable<{0}>", IIf(this.TItem = vbNullString, "Variant", this.TItem))
End Function


### Usage

Here's some test code that shows how the class can be used:

Public Sub TestNullable()

Dim n As New Nullable
Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

n = False
Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

n = True
Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

Set n.Value = Nothing
Debug.Print StringFormat("{0} | HasValue: {1} | Value: {2}", n.ToString, n.HasValue, n)

On Error Resume Next
n = "test" 'expected "Type mismatch. Expected 'T', 'x' was supplied." error
Debug.Print Err.Description

n = New List 'expected "Value type required. T cannot be an object." error
Debug.Print Err.Description

On Error GoTo 0
End Sub


When called from the immediate pane, this method outputs the following:

TestNullable
Nullable<Variant> | HasValue: False | Value:
Nullable<Boolean> | HasValue: True | Value: False
Nullable<Boolean> | HasValue: True | Value: True
Nullable<Boolean> | HasValue: False | Value:
Type mismatch. Expected 'Boolean', 'String' was supplied.
Value type required. T cannot be an object.


Did I miss anything or this is a perfectly acceptable implementation?

One thing did surprise me: if I do Set n.Value = Nothing, the instance remains a Nullable<Boolean> as expected. However if I do Set n = Nothing, not only Debug.Print n Is Nothing will print False, the instance gets reset to a Nullable<Variant> and ...the setter (Public Property Set Value) does not get called - as a result, I wonder if I have written a class with a built-in bug that makes it un-Nothing-able?

### Bonus

After further testing, I have found that this:

Dim n As New Nullable
Set n = Nothing
Debug.Print n Is Nothing


Outputs False. However this:

Dim n As Nullable
Set n = New Nullable
Set n = Nothing
Debug.Print n Is Nothing


Outputs True (both snippets never hit a breakpoint in the Set accessor).

All these years I thought Dim n As New SomeClass was the exact same thing as doing Dim n As SomeClass followed by Set n = New SomeClass. Did I miss the memo?

# UPDATE

### Don't do this at home.

After a thorough review, it appears an Emptyable<T> in VB6 is absolutely moot. All the class is buying, is a HasValue member, which VB6 already takes care of, with its IsEmpty() function.

Basically, instead of having a Nullable<Boolean> and doing MyNullable.HasValue, just declare a Boolean and assign it to Empty, and verify "emptiness" with IsEmpty(MyBoolean).

• VB6 has the nasty habit of instantiating a new object if a method is called on something that is Nothing. I'm curious to see your test function for the Set n = Nothing bit. I suspect the surprising behavior is there, not in the class itself. – Comintern Feb 14 '14 at 1:31
• @Comintern I've edited with my latest findings (although that's starting to be more on StackOverflow's grounds) – Mathieu Guindon Feb 14 '14 at 1:33
• I thought this deserved a longer-winded explanation. See below. :-) – Comintern Feb 14 '14 at 2:11
• Dim n As New SomeClass is the exact same thing as doing Dim n As SomeClass followed by Set n = New SomeClass. Unfortunately both are the same as Dim n As SomeClass followed by Debug.Print (n) – Comintern Feb 14 '14 at 2:13
• I think that what you suggest in your "UPDATE" section only works if you make MyBoolean a Variant rather than a Boolean. – Jeff Roe Oct 15 '15 at 16:50

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.

• +1 I was just coming to the same conclusions! Wow I did miss the memo... or .NET has successfully "corrupted" my VB6 mind! – Mathieu Guindon Feb 14 '14 at 2:15
• More accurate to say that .NET "uncorrupted" VB6. – Comintern Feb 14 '14 at 2:16
• You seem to know your VB6.. I'd be curious to read your input on my List<T> class :) – Mathieu Guindon Feb 14 '14 at 2:21
• It's where I cut my teeth in programming. I'll take a look at it tonight. You can do some wild stuff with VB6 like in-line assembly and opening files as memory mapped arrays. The wheels really came off when they introduced AddressOf, it lets you break out of the walls of the runtime. – Comintern Feb 14 '14 at 2:26
• Building on your answer I added my own, feel free to comment ;) and PS - feel free to join us (CR regulars) anytime in The 2nd Monitor! – Mathieu Guindon Feb 14 '14 at 3:01

Adding to @Comintern's excellent answer, the private type doesn't need an IsNull member, since the class only accepts value types, the correct semantics for "null" values is vbEmpty.

The Set accessor is therefore not only wrong, it's also ambiguous - not only in attempting to assign Nothing to a value type, but also because Value being the default member, it's not immediately obvious what this does:

Set MyNullable = Nothing


The solution is simple: get rid of the Set accessor altogether:

Private Type tNullable
Value As Variant
TItem As String
End Type

Private this As tNullable
Option Explicit

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

Public Property Let Value(val As Variant)
If ValidateItemType(val) Then this.Value = val
End Property


HasValue can then be rewritten like this:

Public Property Get HasValue() As Boolean
HasValue = Not IsEmpty(this.Value)
End Property


And IsTypeSafe should accept type name "Empty":

Private Function IsTypeSafe(val As Variant) As Boolean
IsTypeSafe = this.TItem = vbNullString _
Or this.TItem = TypeName(val) _
Or TypeName(val) = "Empty"
End Function


As a result we can now do this:

Dim n As New Nullable
n = False 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns True
n = Empty 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns False
Set n = Nothing 'n.ToString returns "Nullable<Variant>"; n.HasValue returns False


And now the bad naming for the class becomes more than just obvious.

The ToString method should therefore be tweaked to no longer hard-code the type's name:

Public Function ToString() As String
ToString = StringFormat("{0}<{1}>", TypeName(Me), IIf(this.TItem = vbNullString, "Variant", this.TItem))
End Function


And the class should be renamed to Emptyable... regardless of how ugly that is: VB6 just isn't .NET.

• Value is not always the default member though. It's the runtime that decides which type is the most appropriate. Other than that a definite +1. – user28366 Feb 14 '14 at 8:07
• I see i see :) only recently I have found out that Value is not a default member if there is a _Default property defined in a class. It is the runtime that decides what the default property is going to be. You explicitly use .Value so it should not affect anything which is GOOD but I thought it was worth mentioning :) – user28366 Feb 14 '14 at 12:14