VBA:IsEmpty、vbEmpty、“空”和空

New*_*tes 7 vba is-empty

在VBA中,如果我理解正确的话,空意味着变量尚未初始化,即它是赋值之前变量的默认值。

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

IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
Run Code Online (Sandbox Code Playgroud)

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

看起来它们应该是等价的,但我惊讶地发现微软关于 IsEmptyvbEmpty( 1 , 2 ) 和onTypeName的文档 没有相互引用,我认为如果它们是等价的,它们就会互相引用。

我发现两个参考文献似乎暗示前三个在 VBscript 中是相同的(其中一切都是变体):CodeWikiHerong

似乎有些情况是 Excel 特有的。看来Excel中的空也指的是不包含任何内容的单元格,我认为这相当于表示该单元格未启动的变量。但“决策模型”网站表示,空性还指单元格值是否是最新的(“如果计算出的参数引用了未计算的单元格,则该参数为空”)。但是该页面在一个地方说要测试 using vbEmpty,而在其他地方说要使用IsEmpty

IsEmpty我发现两个 StackOverflow 问题讨论了和Empty( 1 , 2 )的关系,但没有讨论其他两种方法。

应用于数组时似乎也可能存在细微的差异。

我在 GitHub 上找到了以下代码片段,这意味着 ifVarType(Obj) = vbEmpty的值IsEmpty(Obj)仍然可能为 true 或 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
Run Code Online (Sandbox Code Playgroud)

所以,相当混乱。

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

IsEmpty(var) = True
VarType(var) = vbEmpty
TypeName(var) = "Empty"
var = Empty
Run Code Online (Sandbox Code Playgroud)

Cri*_*use 11

以下所有内容均适用于 VBA,无论宿主应用程序(Excel、Word、AutoCAD 等)以及 VB6 和之前的 VB 版本如何。碰巧 Excel 可以很好地处理变体,但无论如何,下面的情况都成立。

变体

在幕后,Variant是一个结构(标记为 union),可用于表示 VB 中的任何其他数据类型和一些特殊值。

布局是:

  • 前 2 个字节(整数大小)保存 VARTYPE
  • 字节 3 到 8 被保留并且主要不使用 - 但 Decimal 使用它们
  • 以下字节可以保存值、指针或标志,并且使用的字节数也根据应用程序位数而变化(例如,指针在 x32 上为 4 字节,在 x64 上为 8 字节)

VarType在Variant 上运行时,结果是前 2 个字节,尽管它们返回为Long4 个字节,但由于 VBA 的内存布局是小端字节序,因此 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
Run Code Online (Sandbox Code Playgroud)

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

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

是空的

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

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

我需要引起注意的是,大多数人不使用该IsEmpty(var) = True语法,因为IsEmpty已经返回了布尔值。我,至少永远不会写类似的东西If IsEmpty(var) = True Then,但总是会写If IsEmpty(var) Then。后者更干净。

变量类型

一些笔记。您可能想知道当我们将非 Variant 传递给VarType函数时会发生什么。好吧,VarName参数的类型是 Variant,因此如果您传递非 Variant,它实际上会被包装在 Variant 中。检查 VBE7.dll 可以发现: VbVarType _stdcall VarType([in] VARIANT* VarName);

请注意上面链接上的注释:

如果传递的对象具有默认属性,则 VarType(object) 返回该对象默认属性的类型。

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

然而,上述注释并不影响VarType(var) = vbEmpty和 的等价性IsEmpty(var),因为后者也会检查对象的默认成员。

空的

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

因此,比较一个变体Empty就像比较 2 个变体一样。比较 2 个变体时,有一些适用的特殊规则。这里说明:

如果 expression1 和 expression2 都是 Variant 表达式,则它们的基础类型决定了它们的比较方式。下表显示了如何比较表达式或比较结果,具体取决于 Variant 的基础类型。

如果 然后
两个 Variant 表达式都是数字 进行数值比较。
两个 Variant 表达式都是字符串 执行字符串比较。
一个 Variant 表达式是数字,另一个是字符串 数值表达式小于字符串表达式。
一个 Variant 表达式为空,另一个为数字 使用 0 作为 Empty 表达式执行数值比较。
一个 Variant 表达式为空,另一个为字符串 使用零长度字符串 ("") 作为 Empty 表达式执行字符串比较。
两个 Variant 表达式都是空的 表达式是相等的。

所以,var = Empty不等于VarType(var) = vbEmpty/ IsEmpty(var)。简单示例: ifvar是空字符串 ("") 或空字符串 (vbNullString) thenvar = Empty返回TruewhileVarType(var) = vbEmptyIsEmpty(var)两者都返回False

类型名称

TypeName非常不同,因为它返回一个String.

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

这就是为什么您会看到Range参数是包装在 Variant 中的 Excel.Range。实际的 VARTYPEvbObject更进一步TypeName检查对象的类型。

我认为在您的 Excel 示例中,您实际上对该房产感兴趣Range.Value。如果var是一个范围,那么TypeName(var.Value) = "Empty"恰好等于IsEmpty(var.Value)但只是因为该.Value属性永远不会返回对象,但如果它返回,那么它们将不再等效。但是,TypeName(var)永远不会与IsEmpty(var)if varis an object 等效。

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

结论

  • VarType(var) = vbEmpty总是等价于IsEmpty(var).
  • var = Empty遵循比较两个变体的规则,因此与上面的 2 个不同。
  • TypeName(var) = "Empty"仅当不是对象时才与VarType(var) = vbEmpty/等效。IsEmpty(var)var

不见了

只是为了澄清,因为您已经在自己的答案中显示了它,如果变体的类型vbError(前 2 个字节 VT_ERROR)和 SCODE 成员(字节 9 到 12)设置为 DISP_E_PARAMNOTFOUND (0x80020004),那么 VB* 将其视为特殊Missing价值。

以下代码返回特殊的 Missing 值:

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

  • @NewSites 我刚刚提供了 +100 赏金,将用于这篇优秀的帖子 =) (3认同)