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 aList<T> for VB6 (here), and before that I implementedString.Format() and a number of string-helper functions (here). Don't go looking for aStringFormat method in the VB6 language specs, that method is the one I've written.
Today I would have liked to be able to declare aNullable<bool> in VB6, so I implemented a class that allowed me to do that. I named this classNullable and it goes like this:
Private Type tNullable Value As Variant IsNull As Boolean TItem As StringEnd TypePrivate this As tNullableOption ExplicitPrivate Sub Class_Initialize() this.IsNull = TrueEnd SubNow before I go any further I have to mention that I have used "procedure attributes" in theValue property, making it the type'sdefault member:
Public Property Get Value() As Variant'default member Value = this.ValueEnd PropertyPublic Property Let Value(val As Variant) 'damn case-insensitivity...'default member If ValidateItemType(val) Then this.Value = val this.IsNull = False End IfEnd PropertyPublic 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 PropertyTheValidateItemType private method determines whether the type of a value is "ok" to be assigned as the instance'sValue:
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 = resultEnd FunctionPrivate Function IsTypeSafe(val As Variant) As Boolean IsTypeSafe = this.TItem = vbNullString Or this.TItem = TypeName(val)End FunctionThat mechanism is borrowed from theList<T> implementation I wrote before, and proved to be working fine. Shortly put, an instance of theNullable class is aNullable<Variant> until it's assigned a value - if that value is aInteger then the instance becomes aNullable<Integer> and remains of that type - so theValue can only be assigned anInteger.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 areHasValue() andToString():
Public Property Get HasValue() As Boolean HasValue = Not this.IsNullEnd PropertyPublic Function ToString() As String ToString = StringFormat("Nullable<{0}>", IIf(this.TItem = vbNullString, "Variant", this.TItem))End FunctionUsage
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 0End SubWhen called from theimmediate pane, this method outputs the following:
TestNullableNullable<Variant> | HasValue: False | Value: Nullable<Boolean> | HasValue: True | Value: FalseNullable<Boolean> | HasValue: True | Value: TrueNullable<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 doSet n.Value = Nothing, the instance remains aNullable<Boolean> as expected. However if I doSet n = Nothing, not onlyDebug.Print n Is Nothing will printFalse, the instance gets reset to aNullable<Variant> and ...the setter (Public Property Set Value) doesnot 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 NullableSet n = NothingDebug.Print n Is NothingOutputsFalse. However this:
Dim n As NullableSet n = New NullableSet n = NothingDebug.Print n Is NothingOutputsTrue (both snippets never hit a breakpoint in theSet accessor).
All these years I thoughtDim n As New SomeClass was the exact same thing as doingDim n As SomeClass followed bySet n = New SomeClass. Did I miss the memo?
UPDATE
Don't do this at home.
After a thorough review, it appears anEmptyable<T> in VB6 is absolutely moot. All the class is buying, is aHasValue member, which VB6 already takes care of, with itsIsEmpty() function.
Basically, instead of having aNullable<Boolean> and doingMyNullable.HasValue, just declare aBoolean and assign it toEmpty, and verify "emptiness" withIsEmpty(MyBoolean).
- 1\$\begingroup\$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.\$\endgroup\$Comintern– Comintern2014-02-14 01:31:37 +00:00CommentedFeb 14, 2014 at 1:31
- \$\begingroup\$@Comintern I've edited with my latest findings (although that's starting to be more on StackOverflow's grounds)\$\endgroup\$Mathieu Guindon– Mathieu Guindon2014-02-14 01:33:02 +00:00CommentedFeb 14, 2014 at 1:33
- \$\begingroup\$I thought this deserved a longer-winded explanation. See below. :-)\$\endgroup\$Comintern– Comintern2014-02-14 02:11:46 +00:00CommentedFeb 14, 2014 at 2:11
- 1\$\begingroup\$
Dim n As New SomeClassis the exact same thing as doingDim n As SomeClassfollowed bySet n = New SomeClass. Unfortunately both are the same asDim n As SomeClassfollowed byDebug.Print (n)\$\endgroup\$Comintern– Comintern2014-02-14 02:13:53 +00:00CommentedFeb 14, 2014 at 2:13 - \$\begingroup\$I think that what you suggest in your "UPDATE" section only works if you make
MyBooleanaVariantrather than aBoolean.\$\endgroup\$Jeff Roe– Jeff Roe2015-10-15 16:50:57 +00:00CommentedOct 15, 2015 at 16:50
2 Answers2
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 totest 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 runsSet 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 SubVB6 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 hoodhere.
- \$\begingroup\$+1 I was just coming to the same conclusions! Wow Idid miss the memo... or .NET has successfully "corrupted" my VB6 mind!\$\endgroup\$Mathieu Guindon– Mathieu Guindon2014-02-14 02:15:07 +00:00CommentedFeb 14, 2014 at 2:15
- 1\$\begingroup\$More accurate to say that .NET "uncorrupted" VB6.\$\endgroup\$Comintern– Comintern2014-02-14 02:16:31 +00:00CommentedFeb 14, 2014 at 2:16
- 1\$\begingroup\$You seem to know your VB6.. I'd be curious to read your input on my
List<T>class :)\$\endgroup\$Mathieu Guindon– Mathieu Guindon2014-02-14 02:21:04 +00:00CommentedFeb 14, 2014 at 2:21 - \$\begingroup\$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.\$\endgroup\$Comintern– Comintern2014-02-14 02:26:42 +00:00CommentedFeb 14, 2014 at 2:26
- 1\$\begingroup\$Building on your answer I added my own, feel free to comment ;) and PS - feel free to join us (CR regulars) anytime inThe 2nd Monitor!\$\endgroup\$Mathieu Guindon– Mathieu Guindon2014-02-14 03:01:30 +00:00CommentedFeb 14, 2014 at 3:01
Adding to @Comintern's excellent answer, the private type doesn't need anIsNull member, since the class only accepts value types, the correct semantics for "null" values isvbEmpty.
TheSet accessor is therefore not only wrong, it's also ambiguous - not only in attempting to assignNothing to a value type, but also becauseValue being the default member, it's not immediately obvious what this does:
Set MyNullable = NothingThe solution is simple: get rid of theSet accessor altogether:
Private Type tNullable Value As Variant TItem As StringEnd TypePrivate this As tNullableOption ExplicitPublic Property Get Value() As Variant Value = this.ValueEnd PropertyPublic Property Let Value(val As Variant) If ValidateItemType(val) Then this.Value = valEnd PropertyHasValue can then be rewritten like this:
Public Property Get HasValue() As Boolean HasValue = Not IsEmpty(this.Value)End PropertyAndIsTypeSafe 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 FunctionAs a result we can now do this:
Dim n As New Nullablen = False 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns Truen = Empty 'n.ToString returns "Nullable<Boolean>"; n.HasValue returns FalseSet n = Nothing 'n.ToString returns "Nullable<Variant>"; n.HasValue returns FalseAnd now the bad naming for the class becomes more than just obvious.
TheToString 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 FunctionAnd the class should be renamed toEmptyable... regardless of how ugly that is: VB6 just isn't .NET.
- 1\$\begingroup\$Value is not always the default member though. It's theruntime that decides which type is themost appropriate. Other than that a definite +1.\$\endgroup\$user28366– user283662014-02-14 08:07:40 +00:00CommentedFeb 14, 2014 at 8:07
- \$\begingroup\$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
.Valueso it should not affect anything which is GOOD but I thought it was worth mentioning :)\$\endgroup\$user28366– user283662014-02-14 12:14:35 +00:00CommentedFeb 14, 2014 at 12:14
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.

