VBA:IsEmpty、vbEmpty、"Empty" 和 Empty

VBA: IsEmpty, vbEmpty, "Empty" and Empty

在VBA中,如果我没理解错的话,empty是指一个variant没有被初始化,即赋值前variant的默认值。

似乎有四种方法可以测试变体是否为空:

IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty

我想知道的是这些方法是否完全相同,或者是否存在细微(或明显)差异。

看起来他们应该是等价的,但我很惊讶地发现微软的documentation on IsEmpty, on vbEmpty (1, 2), and on TypeName没有互相引用,我认为如果他们是等价的,他们就会这样。

我发现两个引用似乎暗示前三个在 VBscript 中是相同的(其中所有内容都是变体):CodeWiki, Herong.

好像有Excel特有的情况。似乎 Excel 中的空虚也指的是一个不包含任何东西的单元格,我想这相当于表示该单元格未被启动的变量。但是网站 "Decision Models" says emptyness 还指单元格值是否是最新的(“如果计算的参数引用未计算的单元格,则为空”)。但是该页面 在一个地方说要使用 vbEmpty 进行测试,而在其他地方说要使用 IsEmpty.

我发现了两个讨论 IsEmptyEmpty (1, 2) 关系的 Whosebug 问题,但没有讨论其他两个方法。

applied to arrays.

似乎也有细微差别

我发现了下面的code snippet on GitHub,这意味着如果VarType(Obj) = vbEmptyIsEmpty(Obj)的值可能仍然是真或假:

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

所以,很混乱。

总而言之,我的问题是,在VBA中,以下是等价的,或者它们的含义有什么区别?

IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty

好的,我已经在 Excel 中做了一些测试。我不打算接受这个答案,因为我认为这不是我问题的最终答案,因为:

  • 它特定于 Excel,所以我不知道这些结果将如何转移到 Access 和其他 Office 程序。
  • 这只是对各种情况的测试。明确的答案将基于用于计算 IsEmpty()VarTypeTypeName() 以及分配 Empty.
  • 的算法的知识

根据该免责声明,这里是用于测试的 VBA 函数:

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()

以下是使用该函数组成测试的公式:

结果如下:

根据这些结果,我得出以下结论:

  • IsEmpty()VarType() = vbEmpty 在 Excel 中似乎是等价的,因为每当 IsEmpty() 为真或假时, VarType() 分别是, vbEmpty 或不 vbEmpty.
  • IsEmpty()TypeName() = "Empty" 在 Excel 中绝对不完全等价,因为当 IsEmpty() 为真时, TypeName() 可能是也可能不是 "Empty".
  • IsEmpty()Empty 在 Excel 中绝对不完全等价,因为在测试的四个没有导致错误的情况下, IsEmpty() 是真还是假, 但测试变量始终等于 Empty.

因此,在 Excel 中似乎可以互换使用 IsEmpty()VarType() = vbEmpty,但必须注意它们与 TypeName() = "Empty" 之间的差异和 Empty.

根据这些结果,我看不出问题中引用的 GitHub 中的代码是如何工作的。似乎在该代码中,IsEmpty(Obj) 永远不会是 false.

我希望知道 VBA 幕后情况的人能够说出这里真正发生的事情。

以下所有内容都适用于 VBA,无论主机应用程序(Excel、Word、AutoCAD 等)以及 VB6 和之前的 VB 版本。碰巧 Excel 与 Variants 配合得很好,但下面的内容无论如何都适用。

变体

在幕后 Variant is a structure (tagged union),可用于表示 VB 中的任何其他数据类型和一些特殊值。

布局为:

  • 前 2 个字节(整数大小)包含 VARTYPE
  • 字节 3 到 8 是保留的,主要不使用 - 尽管 Decimal 使用它们
  • 以下字节可以保存一个值、一个指针或一个标志,使用的字节数也因应用程序位数而异(例如,指针在 x32 上为 4 个字节,在 x64 上为 8 个字节)

当 运行 VarType 在 Variant 上时,结果是前 2 个字节,尽管它们被 returned 为 Long,这是 4 个字节,但由于 VBA 的内存布局是 little-endian 然后 Long 中的前 2 个字节与 Integer 中的 2 个字节完全重叠。

我们可以用CopyMemoryAPI来演示上面的:

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

VARTYPE 保存数据类型,但也可以设置 VT_BYREF 标志,这意味着 Variant 通过引用(作为参数)传递给当前方法,这有助于 VB 当方法退出作用域时,知道哪些内存需要释放,哪些不需要。 VarType 没有 return VT_BYREF 标志,但这超出了问题范围。还有一个单独的 VT_ARRAY 标志(如 vbArray),它可以与其他标志结合使用来描述数组的内容,例如整数数组将设置 vbArray (VT_ARRAY) 和 vbInteger (VT_I2) 标志(如 vbArray + vbInteger)。

与问题无关但与上述问题相关,VT_BYREF 标志可用于操纵内存,如我的 VBA-MemoryTools 存储库中所示。

IsEmpty

看完上面的内容就很容易理解了。 IsEmpty 函数只是检查 Variant 的 VARTYPE(前 2 个字节)是否为 vbEmpty(即 0)。

所以是的,VarType(var) = vbEmptyIsEmpty(var) = True 这两个条件总是等价的。

我需要提请注意,大多数人不使用 IsEmpty(var) = True 语法,因为 IsEmpty 已经 return 是一个布尔值。我,至少永远不会写If IsEmpty(var) = True Then,而是永远写If IsEmpty(var) Then。后者更干净。

变量类型

一些注意事项。您可能想知道当我们将 non-Variant 传递给 VarType 函数时会发生什么。好吧,VarName 参数属于 Variant 类型,因此如果您传递 non-Variant,它实际上会被包装在 Variant 中。检查 VBE7.dll 揭示了这一点:VbVarType _stdcall VarType([in] VARIANT* VarName);

注意上面link的备注:

If an object is passed and has a default property, VarType(object) returns the type of the object's default property.

这意味着要检查您需要使用 IsObject 的对象,它会检查 VARTYPE 字节是否设置为 vbObject。在这种特殊情况下(对象),两个 VarType(var) = vbObjectIsObject(var) 并不总是等价的。

但是,上面的注释不影响VarType(var) = vbEmptyIsEmpty(var)的等价性,因为后者也会检查对象的默认成员。

在VB*中,Empty只是一个关键字,但相当于前2个字节设置为vbEmpty的Variant。它的存在方式与 Null 相同(前 2 个字节设置为 vbNull 的变体)。

因此,将变体与 Empty 进行比较就像比较 2 个变体。比较 2 个变体时,有一些特殊规则适用。表示 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.

因此,var = Empty 不等同于 VarType(var) = vbEmpty/IsEmpty(var)。快速示例:如果 var 是空字符串 ("") 或空字符串 (vbNullString),则 var = Empty returns TrueVarType(var) = vbEmptyIsEmpty(var) 两者 return False.

类型名称

TypeName 完全不同,因为它 return 是 String

与对象一起使用时非常有用。例如,如果 var 是一个集合,则 VarType(var) returns vbObjectTypeName(var) returns Collection。因此,TypeName 提供了更多信息。与数组相同:TypeName(Array()) returns Variant() 但取决于数组类型,它可以 return Integer()Double() 等等。

这就是为什么当您的参数是 Excel.Range 包装在 Variant 中时您会看到 Range 的原因。实际的 VARTYPE 是 vbObjectTypeName 更进一步并检查对象的类型。

我认为在您的 Excel 示例中,您实际上对 Range.Value 属性 感兴趣。如果 var 是一个范围,那么 TypeName(var.Value) = "Empty" 恰好等同于 IsEmpty(var.Value),但这只是因为 .Value 属性 从来不是 return 对象而是如果是这样,那么它们将不再等效。但是,如果 var 是一个对象,TypeName(var) 永远不会等同于 IsEmpty(var)

请注意 TypeName 不会查看对象的默认成员。

结论

  • VarType(var) = vbEmpty 始终等同于 IsEmpty(var).
  • var = Empty 遵循比较两个变体的规则,因此不等同于上面的 2。
  • 如果 var 不是对象,
  • TypeName(var) = "Empty" 仅等同于 VarType(var) = vbEmpty/IsEmpty(var)

缺失

澄清一下,因为您已经在自己的答案中展示了变体是否具有 vbError 类型(前 2 个字节 VT_ERROR)和 SCODE 成员(字节 9 到 12 ) 设置为 DISP_E_PARAMNOTFOUND (0x80020004) 然后 VB* 将其视为特殊的 Missing 值。

以下代码return特殊缺失值:

Public Function Missing() As Variant
    Missing = &H80020004 'Sets bytes 9 to 12
    CopyMemory Missing, vbError, 2 'Sets first 2 bytes
End Function