In VBA, if I understand correctly, emptiness means that a variant has not been initialized, i.e., it is the default value of a variant before an assignment.
There appear to be four ways to test if a variant is empty:
IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
What I want to know is if those methods are completely equivalent, or if there are subtle (or stark) differences.
It seems that they ought to be equivalent, but I was surprised to find that Microsoft's documentation on IsEmpty, on vbEmpty (1, 2), and on TypeName make no reference to each other, which I thought they would if they are equivalent.
I found two references that seem to imply the first three are the same in VBscript (where everything is a variant): CodeWiki, Herong.
It seems that there are situations that are specific to Excel. It appears that emptiness in Excel also refers to a cell not containing anything, which I suppose is equivalent to the variable representing that cell not being initiated. But the website "Decision Models" says that emptiness also refers to whether a cell value is up to date ("a calculated parameter is Empty if it references uncalculated cells"). But that page says in one place to test for that using vbEmpty and in other places says to use IsEmpty.
I found two StackOverflow questions that discuss the relationship of IsEmpty and Empty (1, 2), but not on the other two methods.
It also seems that there might be subtle differences when applied to arrays.
I found the following code snippet on GitHub, which implies that if VarType(Obj) = vbEmpty, the value of IsEmpty(Obj) may still be either true or false:
Select Case VarType(Obj)
Case vbNull
json_toString = "null"
Case vbEmpty
'dkottow check if the cell is empty to evtl. convert to null
If IsEmpty(Obj) Then
json_toString = "null"
Else
json_toString = """"""
End If
So, pretty confusing.
To summarize, my question is, in VBA, are the following equivalent, or what are the differences in their meanings?
IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
All of the below is applicable to VBA regardless of the host application (Excel, Word, AutoCAD etc.) as well as VB6 and prior VB versions. It just happens that Excel works well with Variants but the below hold true regardless.
Variant
Behind the scene a Variant is a structure (tagged union) and can be used to represent any other data type in VB and a couple of special values.
The layout is:
the first 2 bytes (Integer size) hold the VARTYPE
bytes 3 to 8 are reserved and mainly not used - Decimal uses them though
the following bytes can hold a value, a pointer or a flag and the number of bytes used also varies depending on application bitness (for example a pointer is 4 bytes on x32 and 8 bytes on x64)
When running VarType on a Variant the result is those 2 first bytes although they are returned as Long which is 4 bytes but since VBA's memory layout is little-endian then first 2 bytes in a Long perfectly overlap with the 2 bytes in an Integer.
We can use the CopyMemory API to demonstrate the above:
Option Explicit
#If Mac Then
#If VBA7 Then
Public Declare PtrSafe Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As LongPtr) As LongPtr
#Else
Public Declare Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As Long) As Long
#End If
#Else 'Windows
'https://msdn.microsoft.com/en-us/library/mt723419(v=vs.85).aspx
#If VBA7 Then
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
#Else
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
#End If
#End If
Sub TestVariantVT()
Dim v As Variant
Dim vt As Integer
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbEmpty
v = CInt(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbInteger
v = CLng(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbLong
v = CDbl(2)
CopyMemory vt, v, 2
Debug.Assert vt = VarType(v) 'vbDouble
End Sub
The VARTYPE holds the data type but can also have the VT_BYREF flag set which means that the Variant is passed by reference (as an argument) to the current method which helps VB know what memory needs to be deallocated and which does not, when the method exits scope. VarType does not return the VT_BYREF flag but this is outside of the question scope. There is also a separate VT_ARRAY flag (as in vbArray) which can be used in combination with other flags to describe the contents of an array e.g. an array of integers will have the vbArray (VT_ARRAY) and the vbInteger (VT_I2) flags set (as in vbArray + vbInteger).
Unrelated to the question but related with the above, the VT_BYREF flag can be used to manipulate memory as seen in my VBA-MemoryTools repository.
IsEmpty
This is quite easy to understand once you've read the above. The IsEmpty function simply checks if the VARTYPE (first 2 bytes) of the Variant is vbEmpty (which is 0).
So yes, the 2 conditions VarType(var) = vbEmpty and IsEmpty(var) = True are always equivalent.
I need to draw attention that most people don't use the IsEmpty(var) = True syntax because IsEmpty already returns a boolean. I, at least will never write something like If IsEmpty(var) = True Then but instead will always write If IsEmpty(var) Then. The latter is cleaner.
VarType
A few notes. You may be wondering what happens when we pass a non-Variant to the VarType function. Well, the VarName argument is of type Variant and so if you pass a non-Variant it actually gets wrapped in a Variant. Inspecting the VBE7.dll reveals this: VbVarType _stdcall VarType([in] VARIANT* VarName);
Note the remark on the link above:
If an object is passed and has a default property, VarType(object) returns the type of the object's default property.
This means that to check for objects you need to use IsObject which checks if the VARTYPE bytes are set to vbObject. In this particular case (objects) the two VarType(var) = vbObject and IsObject(var) are not always equivalent.
However, the above remark does not influence the equivalence of VarType(var) = vbEmpty and IsEmpty(var) because the latter will also check an object's default member.
Empty
In VB*, Empty is just a keyword but is the equivalent of a Variant with the first 2 bytes set to vbEmpty. It's there for convenience in the same way Null is (Variant with first 2 bytes set to vbNull).
Hence, comparing a Variant with Empty is like comparing 2 Variants. When comparing 2 Variants, there are some special rules that apply. Stated here:
If expression1 and expression2 are both Variant expressions, their underlying type determines how they are compared. The following table shows how the expressions are compared or the result from the comparison, depending on the underlying type of the Variant.
If
Then
Both Variant expressions are numeric
Perform a numeric comparison.
Both Variant expressions are strings
Perform a string comparison.
One Variant expression is numeric and the other is a string
The numeric expression is less than the string expression.
One Variant expression is Empty and the other is numeric
Perform a numeric comparison, using 0 as the Empty expression.
One Variant expression is Empty and the other is a string
Perform a string comparison, using a zero-length string ("") as the Empty expression.
Both Variant expressions are Empty
The expressions are equal.
So, var = Empty is NOT the equivalent of VarType(var) = vbEmpty/IsEmpty(var). Quick example: if var is an empty string ("") or a null string (vbNullString) then var = Empty returns True while VarType(var) = vbEmpty and IsEmpty(var) both return False.
TypeName
TypeName is quite different as it returns a String.
It is quite useful when used with objects. For example if var is a Collection then VarType(var) returns vbObject while TypeName(var) returns Collection. So, TypeName offers some more information. Same with arrays: TypeName(Array()) returns Variant() but depending on the array type it can return Integer() or Double() and so on.
That's why you are seeing Range when your parameter is an Excel.Range wrapped in a Variant. The actual VARTYPE is vbObject but TypeName goes a step further and checks the type of the object.
I think in your Excel example you are actually interested in the Range.Value property. If var is a Range then TypeName(var.Value) = "Empty" is just by coincidence equivalent with IsEmpty(var.Value) but only because the .Value property never returns an object but if it did then they would not be equivalent anymore. However, TypeName(var) will never be equivalent with IsEmpty(var) if var is an object.
Note that TypeName does not look at the default member of an object.
Conclusion
VarType(var) = vbEmpty is always the equivalent of IsEmpty(var).
var = Empty follows the rules of comparing two variants and so is not equivalent with the 2 above.
TypeName(var) = "Empty" is only equivalent with VarType(var) = vbEmpty/IsEmpty(var) if var is NOT an object.
IsMissing
Just to clarify, because you've shown it in your own answer, if a variant has the vbError type (first 2 bytes VT_ERROR) and the SCODE member (bytes 9 to 12) set to DISP_E_PARAMNOTFOUND (0x80020004) then VB* sees it as the special Missing value.
The following code returns the special Missing value:
Public Function Missing() As Variant
Missing = &H80020004 'Sets bytes 9 to 12
CopyMemory Missing, vbError, 2 'Sets first 2 bytes
End Function
Okay, I've done some testing in Excel. I don't intend to accept this answer because I don't think it's a definitive answer to my question because:
It's specific to Excel, so I don't know how these results will carry over to Access and other Office programs.
It's just a test of a variety of cases. A definitive answer would be based on knowledge of the algorithms used to calculate IsEmpty(), VarType, and TypeName(), and to assign Empty.
With that disclaimer, here is the VBA function used for the test:
Function vTestEmptiness(sCellOrVar As String, sTest As String, Optional vCell As Variant) As Variant
Dim vVar As Variant
Select Case sCellOrVar
Case "Cell":
Select Case sTest
Case "IsEmpty": vTestEmptiness = IsEmpty(vCell)
Case "VarType": vTestEmptiness = Choose(VarType(vCell) + 1, _
"vbEmpty", "", "", "", "", "vbDouble", _
"", "", "vbString", "", "vbError")
Case "TypeName": vTestEmptiness = TypeName(vCell)
Case "Empty": vTestEmptiness = (vCell = Empty)
Case "IsNull": vTestEmptiness = IsNull(vCell)
Case "IsMissing": vTestEmptiness = IsMissing(vCell)
End Select
Case "Var":
Select Case sTest
Case "IsEmpty": vTestEmptiness = IsEmpty(vVar)
Case "VarType": vTestEmptiness = Choose(VarType(vVar) + 1, _
"vbEmpty", "", "", "", "", "vbDouble", _
"", "", "vbString", "", "vbError")
Case "TypeName": vTestEmptiness = TypeName(vVar)
Case "Empty": vTestEmptiness = (vVar = Empty)
Case "IsNull": vTestEmptiness = IsNull(vVar)
Case "IsMissing": vTestEmptiness = IsMissing(vVar)
End Select
End Select
End Function ' vTestEmptiness()
Here are the formulas that make up the test using that function:
And here are the results:
From those results, I conclude the following:
IsEmpty() and VarType() = vbEmpty appear to be equivalent in Excel in the sense that whenever IsEmpty() is true or false, VarType() is, respectively, vbEmpty or not vbEmpty.
IsEmpty() and TypeName() = "Empty" are definitely not completely equivalent in Excel because when IsEmpty() is true, TypeName() may or may not be "Empty".
IsEmpty() and Empty are definitely not completely equivalent in Excel because in the four cases tested that did not result in an error, IsEmpty() was true or false, but the tested variable was always equal to Empty.
And so, it seems that in Excel, one can use IsEmpty() and VarType() = vbEmpty interchangeably, but one has to be careful about differences between those and TypeName() = "Empty" and Empty.
From these results, I don't see how the code from GitHub cited in my question works. It seems that in that code, IsEmpty(Obj) would never be false.
I hope someone who knows what's going on under the hood in VBA will speak up about what's really going on here.
Related
I am developing an Access DB for a little project at work. We have an ODBC Database that has a list of certain servers entered into a web client. I tried writing a VBA function to take them, convert them to lowercase, and assign the string to "true" if they match, and "false" if not. I use the function in a query so you can search for ranges of server and so-on and so-forth.
Public Function InWhatsUp(field1 As Field, field2 As Field) As String
If (LCase(field1.Value) = LCase(field2.Value)) Then
InWhatsUp = "True"
Else
InWhatsUp = "False"
End If
Return
End Function
I haven't been able to find much relevant research besides using an IIF to compare, but the results all turn up as "false" there as well. Thank you in advance
A query can never pass objects like Field to a VBA function.
Use parameter data type String, or if the values can be NULL, Variant.
Since normal string comparison is always usually (see below) case-insensitive, you can then simply do
InWhatsUp = (str1 = str2)
or directly in the query:
Identical: IIf(server1 = server2, "True", "False")
If you need case-sensitive string comparison, you must use
StrComp(string1, string2, vbBinaryCompare)
Edit
normal string comparison is always case-insensitive
That's only true if you keep the Option Compare Database that is by default inserted in Access VBA modules.
If you change it to Option Compare Binary or remove it altogether, string comparisons are case-sensitive.
Thanks #ThunderFrame for the correction.
I'd make that function return a Boolean. Its job is to determine a Boolean result - whoever calls it can then decide to turn that Boolean into a String, but most callers would normally run Boolean logic with it, and if that's the case then returning a String requires VBA to perform implicit type conversions all over the place.
And since it's a string utility function, it doesn't even need to know about a Field - give it two Variant values so it can take a Null, and pass them by value:
Public Function AreTheSame(ByVal string1 As Variant, ByVal string2 As Variant) As Boolean
If IsNull(string1) Or IsNull(string2) Then Exit Function
AreTheSame = (StrComp(string1, string2, vbTextCompare) = 0)
End Function
Now you can test this function anywhere, with any input:
?AreTheSame(Null, "ABC") -> False
?AreTheSame(Null, Null) -> False
?AreTheSame("abc", "ABC") -> True
If you want to treat to Null values as equal, then you need to coalesce them into empty strings - and since null-coalescing is a concern of its own, I'd make another, separate function for that:
Public Function Coalesce(ByVal value As Variant) As String
If IsNull(value) Then
Coalesce = vbNullString
Else
Coalesce = CStr(value)
End If
End Function
Public Function AreTheSame(ByVal string1 As Variant, ByVal string2 As Variant) As Boolean
AreTheSame = (StrComp(Coalesce(string1), Coalesce(string2), vbTextCompare) = 0)
End Function
With that, two null values are treated as empty strings. I'd probably use an optional parameter to make that behavior configurable from the call site - ditto for case-sensitivity.
Public Function InWhatsUp(field1 As Variant, field2 As Variant) As Boolean
If IsNull(field1) Or IsNull(field2) Then Exit Function
If (Len(field1) = False) Or (Len(field2) = False) Then Exit Function
If IsError(field1) Or IsError(field2) Then Exit Function
InWhatsUp = StrComp(field1, field2, vbTextCompare)
End Function
Public Sub TestMe()
Debug.Print InWhatsUp("aaB", "aAb")
Debug.Print InWhatsUp("aaB", "abb")
Debug.Print InWhatsUp(Null, "")
End Sub
You do not have return in vba. Instead you refer to the function.
It is a good idea to make the function Boolean, if the only two values to return are True and False.
As far as you are working in Access, probably there could be one exception - if the two strings are "", probably you would like to see False, depending on your business logic. The IsError() is due to the same logic.
Checking whether the input is Null is something that is quite handy in Access. And following the logic of plenty of languages, a Null is never the same as another Null.
Coming from Basic boolean logic in C#, I was wondering why:
Dim b As Boolean
Dim obj As Object = Nothing
'followig evaluates to False'
b = DirectCast(Nothing, Boolean)
'This throws an "Object reference not set to an instance of an object"-Exception'
b = DirectCast(obj, Boolean)
A CType(obj, Boolean) would evaluate to False(just as CBool(obj)). I think it is because the compiler uses a helper function, but that is not my theme.
Why does casting Nothing to Boolean evaluates to False, whereas casting an object that is Nothing to Boolean throws an Nullreference-Exception? Does that make sense?
[Option Strict ON]
Presumably, this is because Nothing in VB.NET is not exactly the same thing as null in C#.
In the case of value types, Nothing implies the default value of that type. In the case of a Boolean, the default value is False, so the cast succeeds.
One of the primary differences between value types such as Integer or structures and reference types such as Form or String is that reference types support a null value. That is to say, a reference type variable can contain the value Nothing, which means that the variable doesn't actually refer to a value. In contrast, a value type variable always contains a value. An Integer variable always contains a number, even if that number is zero. If you assign the value Nothing to a value type variable, the value type variable just gets assigned its default value (in the case of Integer, that default value is zero). There is no way in the current CLR to look at an Integer variable and determine whether it has never been assigned a value - the fact that it contains zero doesn't necessarily mean that it hasn't been assigned a value.
–The Truth about Nullable Types and VB...
EDIT: For further clarification, the reason the second example throws a NullReferenceException at run-time is because the CLR is attempting to unbox the Object (a reference type) to a Boolean. This fails, of course, because the object was initialized with a null reference (setting it equal to Nothing):
Dim obj As Object = Nothing
Remember that, as I explained above, the VB.NET keyword Nothing still works the same way as null in C# when it comes to reference types. That explains why you're getting a NullReferenceException because the object you're attempting to cast is literally a null reference. It does not contain a value at all, and therefore cannot be unboxed to a Boolean type.
You don't see the same behavior when you try to cast the keyword Nothing to a Boolean, i.e.:
Dim b As Boolean = DirectCast(Nothing, Boolean)
because the keyword Nothing (this time, in the case of value types) simply means "the default value of this type". In the case of a Boolean, that's False, so the cast is logical and straightforward.
There's a couple of things you have to realize here.
The first is what others have already pointed out: Nothing can be interpreted by the VB compiler as simply the Boolean value False given the proper context, such as Dim b As Boolean = Nothing.
This means that when the compiler sees this:
b = DirectCast(Nothing, Boolean)
It sees a literal (Nothing) and also sees that you want to use this literal as a Boolean. That makes it a no-brainer.
But now here's the second thing you have to realize. DirectCast on an Object is essentially an unboxing operation (for value types). So what needs to happen from the VB compiler's perspective is: there needs to be a Boolean in that box, or else the operation will fail. Since there is in fact nothing in the box—and this time we're really talking nothing, as in null—it throws an exception.
If I were to translate this code to C#, it would look like this:
bool b;
object obj = null;
b = (bool)default(bool);
b = (bool)obj;
Hopefully that makes things a bit clearer?
There's a difference between using the keyword (literal) Nothing, and using a reference variable whose value is Nothing.
In VB.NET, the literal (keyword) Nothing gets special treatment. The Nothing keyword can be automatically converted into a value type, using the default value of that type.
A reference variable whose value is Nothing is different. You don't get the special behaviour.
The documentation says DirectCast "requires an inheritance or implementation relationship between the data types of the two arguments".
Clearly Object does not inherit from or implement Boolean, unless you have put a boxed Boolean into an object variable.
So the code below fails at runtime with an exception.
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean)
To get the expected behavior, you need this code:
Dim b As Boolean?
Dim obj As Object = Nothing
b = DirectCast(obj, Boolean?)
The character ? mean Nullable(of ).
I'm finding that comparing of the Boolean variable to a string of "True", "False" or Is nothing seems to ensure that I get the correct comparisons. I was using a function to return an html string of a div with an image of a checked or unchecked radio button and was having the issue of nothing coming back as false. Using the variable = "True" or "False" string and doing the last check with IS NOTHING helped to resolve that issue.
Dim b as boolean = nothing
response.write CheckValue(b = "True")
response.write (b = "False")
response.write (b is nothing)
Function CheckValue(inVal as boolean) as string
if inVal then
return ("<div><img src="checked.png" ></div>
else
return ("<div><img src="unchecked.png" ></div>
end if
end function
The system seems to do the conversion to string when implicitly compared to a string whereas using the .tostring method just creates an error while allowing the last comparison to actually compare to a value of nothing.
Hopefully that helps somewhat. It at least let me
What is the difference between these two code lines
Debug.Print VBA.Str(123)
and
Debug.Print VBA.Str$(123)
Str$ and Str are identical except for at least 2 important differences:
If the usage of Str$ returns anything other than a String, you'll get an error:
Debug.Print Str(Null) 'Prints Null
Debug.Print Str$(Null) 'Throws Runtime Error 94 - Invalid use of Null
Str$ returns a String, whereas Str returns a Variant. In general, you should always prefer the strongly typed Str$ over Str
There are many other functions that use $-suffixes, like Left$, Mid$ and Space$.
If we look at Left$ (which is really just an alias for _stdcall _B_str_Left) and Left (which is really just an alias for _stdcall _B_var_Left), in the Type Library MIDL, we see that the input types matter too.
[entry(616), helpcontext(0x000f6ea1)]
BSTR _stdcall _B_str_Left(
[in] BSTR String,
[in] long Length);
[entry(617), helpcontext(0x000f653e)]
VARIANT _stdcall _B_var_Left(
[in] VARIANT* String,
[in] long Length);
You can actually use the underlying functions in code:
Debug.Print VBA.Strings.[_B_var_Left]("abc",1) 'Prints a
Debug.Print VBA.Strings.[_B_str_Left]("abc",1) 'Prints a
And to make matters more confusing, a function like Join (that does return a String, but which doesn't have a corresponding Join$ function, can actually be used with a $ suffix:
Debug.Print Join$(Array(1, 2, 3), ".") 'Prints 1.2.3
For further discussion, see my answer at Exactly what are these _B_var_Xxxxx and _B_str_Xxxxx members in the VBA COM libraries?
Str$ is identical to Str except for the declared type of its return value. i.e. Str returns a Variant, the Str$ returns a String.
Refer the msdn link
I have a VBA class that contains a number of variants. These variants are there to hold Y x Z size arrays, but they're optional inputs (and so often the variants will never get used or initialized).
A function within the same class calls on each of these variants to perform a calculation. BUT, I need it to skip the variants that are blank. When I try to use IsNull(xyz) and IsEmpty(xyz), both return false. Looking at the watch on xyz, it has:
Expression: xyz
Value:
Type: Variant/Variant()
Context: className
Totally blank under value.
Any ideas how I can test/return a boolean if these things are empty? Or even a boolean if they're full, that would work too.
Thanks
Edit: I should add, that IsMissing just completely crashes excel...
Very dirty workaround but something like this might do the trick:
Function IsEmptyArray(testArr As Variant) As Boolean
Dim test As Long
Dim ret As Boolean
ret = False
On Error Resume Next
test = UBound(testArr)
If Err.Number = 9 Then
ret = True
End If
Err.Clear
On Error GoTo 0
IsEmptyArray = ret
End Function
One method I've used in the past is to test whether or not the array is filled using a helper function. I join the array using an empty string delimiter and test that the length is greater than zero. It has worked in my circumstances, but I'm not sure if there are any flaws in the logic.
Below code returns true if the array is empty and false if it is not:
Function TestIfArrayIsEmpty(vArray As Variant) As Boolean
TestIfArrayIsEmpty = (Len(Join(vArray, "")) = 0)
End Function
You can use vartype(variable_name. You can check
vartype(varriable_name) = vbArray or VbEmpty or VbNull
Then check LBound and UBound of eac dimension
for example
LBound(variable, 1) and UBound(variable_name, 1) to check the lower and higher indexes of the array.
I'm was recently trying to redefine Access's Nz(Value, [ValueIfNull]) function in Excel, because I find it pretty useful yet it's absent in Excel (as I found to my disappointment when moving a few useful functions over). Access's Nz function checks Value - if this value is null, it returns ValueIfNull (otherwise it returns Value). In Access it's useful for checking the value of input boxes (amongst several other things):
If Nz(myTextBox.Value, "") = "" Then
MsgBox "You need to enter something!"
End If
Rolling my own Nz function didn't seem difficult:
Public Function Nz(value As Variant, Optional valueIfNull As Variant = "") As Variant
If IsNull(value) Then
Nz = valueIfNull
Else
Nz = value
End If
End Function
But as soon as I try to call it with anything that's actually null, Excel complains about it on the calling line (Run-time error '91': Object variable or With block not set, which I understand to be roughly equivilant to a NullReferenceException in other languages), before even getting to the Nz function body. For example Nz(someObj.Value, "") will only work if someObj.Value isn't null (rendering the function entirely moot).
Am I missing some detail of VBA here? Coming from languages like VB.NET, it seems very confusing - I understand object references to be simply addresses to an actual object residing in memory, and so passing around the reference (not the object) shouldn't cause issue (until you try to actually do something with the non-existant object, of course). For eg:
Dim myObj As SomeObject
SomeMethod(myObj) 'the call itself is fine
Public Sub SomeMethod(SomeObject obj)
myObj.DoSomething() 'but *here* it would crash
End Sub
How can you create subs and functions in VBA that will accept a null parameter?
see this and that if anything is still unclear and try
Sub Main()
Dim obj As Range
Debug.Print Nz(obj)
Dim v As Variant
v = Null
Debug.Print Nz(v)
End Sub
Public Function Nz(value As Variant, Optional valueIfNull As Variant = "") As Variant
' deal with an object data type, vbObject = 9
If VarType(value) = vbObject Then
If value Is Nothing Then
Nz = valueIfNull
Else
Nz = value
End If
' deal with variant set to null, vbNull is a Variant set to null
ElseIf VarType(value) = vbNull Then
If IsNull(value) Then
Nz = valueIfNull
Else
Nz = value
End If
End If
End Function