如何将变体分配给 VBA 中的变体?

How can I assign a Variant to a Variant in VBA?

(警告:虽然乍一看可能像一个,但这不是初学者级别的问题。如果您熟悉短语"Let coercion" 或者您曾经研究过 VBA 规范,请继续阅读。)

假设我有一个 Variant 类型的表达式,我想将它赋值给一个变量。听起来很简单,对吧?

Dim v As Variant

v = SomeMethod()    ' SomeMethod has return type Variant

不幸的是,如果 SomeMethod return 是一个对象(即 VarType 为 vbObject 的变体),Let coercion 开始并且 v 包含 "Simple data value" 的对象。换句话说,如果 SomeMethod return 是对 TextBox 的引用,v 将包含一个字符串。

显然,解决方案是使用 Set:

Dim v As Variant

Set v = SomeMethod()

不幸的是,如果 SomeMethod 不是 return 对象,例如一个字符串,产生 类型不匹配 错误。

到目前为止,我找到的唯一解决方案是:

Dim v As Variant

If IsObject(SomeMethod()) Then
    Set v = SomeMethod()
Else
    v = SomeMethod()
End If

它有调用 SomeMethod 两次的不幸副作用。

有没有不需要需要调用SomeMethod两次的解决方案?

在 VBA 中,将 Variant 分配给您不知道它是对象还是基元的变量的唯一方法是将其作为参数传递。

如果您无法重构您的代码,以便将 v 作为参数传递给 Sub、Function 或 Let 属性(尽管 Let 这也适用于对象) ,您始终可以在模块范围内声明 v 并拥有一个专用的 Sub 仅用于保存分配该变量的目的:

Private v As Variant

Private Sub SetV(ByVal var As Variant)
    If IsObject(var) Then
        Set v = var
    Else
        v = var
    End If
End Sub

在其他地方调用 SetV SomeMethod()

不漂亮,但这是唯一不调用 SomeMethod() 两次或触及其内部工作原理的方法。


编辑

好的,我仔细考虑了一下,我认为我找到了一个更接近您的想法的更好的解决方案:

Public Sub LetSet(ByRef variable As Variant, ByVal value As Variant)
    If IsObject(value) Then
        Set variable = value
    Else
        variable = value
    End If
End Sub

[...] I guess there just is no LetSet v = ... statement in VBA

现在有:LetSet v, SomeMethod()

您没有 return 值需要 LetSet 给一个变量,这取决于它的类型,而是传递应该保存 return 值作为第一个参数 通过引用 以便 Sub 可以更改其值。

您可以使用错误捕获来减少预期的方法调用次数。首先尝试设置。如果成功——没问题。否则,只需分配:

Public counter As Long

Function Ambiguous(b As Boolean) As Variant
    counter = counter + 1
    If b Then
        Set Ambiguous = ActiveSheet
    Else
        Ambiguous = 1
    End If
End Function

Sub test()
    Dim v As Variant
    Dim i As Long, b As Boolean

    Randomize
    counter = 0
    For i = 1 To 100
        b = Rnd() < 0.5
        On Error Resume Next
            Set v = Ambiguous(b)
            If Err.Number > 0 Then
                Err.Clear
                v = Ambiguous(b)
            End If
        On Error GoTo 0
    Next i
    Debug.Print counter / 100

End Sub

当我 运行 代码时,我第一次得到 1.55,这比你重复实验时得到的 2.00 要小,但错误处理方法被天真的 if-then-else 您在问题中讨论的方法。

请注意,函数 return 作为对象的频率越高,平均调用的函数就越少。如果它几乎总是 return 一个对象(例如,它应该 return 但 return 在某些情况下是一个描述错误情况的字符串)那么这种做事方式将接近每个设置/分配变量调用 1 次。另一方面——如果它几乎总是 return 是一个原始值,那么每次赋值你将接近 2 次调用——在这种情况下,也许你应该重构你的代码。

It appears that I wasn't the only one with this issue.

解决方法给我了here

简而言之:

Public Declare Sub VariantCopy Lib "oleaut32.dll" (ByRef pvargDest As Variant, ByRef pvargSrc As Variant)
Sub Main()
  Dim v as Variant
  VariantCopy v, SomeMethod()
end sub

这似乎类似于答案中描述的 LetSet() 函数,但我认为这无论如何都会有用。

Dim v As Variant
For Each v In Array(SomeMethod())
    Exit For 'Needed for v to retain it's value
Next v
'Use v here - v is now holding a value or a reference
Dim v As Variant
Dim a As Variant
a = Array(SomeMethod())
If IsObject(a(0)) Then
    Set v = a(0)
Else
    v = a(0)
End If