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)
.
Dim n As New SomeClass
is the exact same thing as doingDim n As SomeClass
followed bySet n = New SomeClass
. Unfortunately both are the same asDim n As SomeClass
followed byDebug.Print (n)
– Comintern Feb 14 '14 at 2:13