如何将变体分配给 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 值需要 Let
或 Set
给一个变量,这取决于它的类型,而是传递应该保存 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
(警告:虽然乍一看可能像一个,但这不是初学者级别的问题。如果您熟悉短语"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 值需要 Let
或 Set
给一个变量,这取决于它的类型,而是传递应该保存 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