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

如何将变量分配给VBA中的变量?,vba,types,variant,Vba,Types,Variant,(警告:虽然乍一看可能像一个问题,但这不是初级问题。如果您熟悉短语“让强制”或您曾经研究过VBA规范,请继续阅读。) 假设我有一个Variant类型的表达式,我想把它赋给一个变量。听起来很简单,对吧 Dim v As Variant v = SomeMethod() ' SomeMethod has return type Variant 不幸的是,如果SomeMethod返回一个对象(即,带有VarType的vbObject的变量),则会启动并v包含该对象的“简单数据值”。换句话说,

(警告:虽然乍一看可能像一个问题,但这不是初级问题。如果您熟悉短语“让强制”或您曾经研究过VBA规范,请继续阅读。)

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

Dim v As Variant

v = SomeMethod()    ' SomeMethod has return type Variant
不幸的是,如果
SomeMethod
返回一个对象(即,带有VarType的vbObject的变量),则会启动并
v
包含该对象的“简单数据值”。换句话说,如果SomeMethod返回对文本框的引用,
v
将包含一个字符串

显然,解决方案是使用
Set

Dim v As Variant

Set v = SomeMethod()
不幸的是,如果
SomeMethod
不返回对象(例如字符串),则此操作将失败,从而产生类型不匹配错误

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

Dim v As Variant

If IsObject(SomeMethod()) Then
    Set v = SomeMethod()
Else
    v = SomeMethod()
End If
这会产生不幸的副作用,调用
SomeMethod
两次


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

在VBA中,在不知道变量是对象还是原语的情况下,将变量指定给变量的唯一方法是将其作为参数传递

如果无法重构代码,从而将
v
作为参数传递给Sub、函数或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
[…]我想根本就没有LetSet v=。。。VBA中的语句

现在有:
letv,SomeMethod()


您不需要根据变量的类型将返回值设置为变量,而是通过引用传递应包含返回值的变量作为第一个参数,以便子对象可以更改其值。

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

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
公共计数器的长度
函数不明确(b作为布尔值)作为变量
计数器=计数器+1
如果b那么
Set=ActiveSheet
其他的
不明确=1
如果结束
端函数
子测试()
Dim v作为变体
Dim i等于长,b等于布尔值
随机化
计数器=0
对于i=1到100
b=Rnd()<0.5
出错时继续下一步
设置v=不明确(b)
如果错误编号>0,则
呃,明白了
v=不明确(b)
如果结束
错误转到0
接下来我
调试。打印计数器/100
端接头
当我运行代码时,第一次得到1.55,这比你重复实验时得到的2.00要小,但是错误处理方法被你在问题中讨论的天真的
if-then-else
方法所取代

请注意,函数返回对象的频率越高,平均而言函数调用的次数就越少。如果它几乎总是返回一个对象(例如,这是它应该返回的对象,但在某些情况下返回一个描述错误条件的字符串),那么这种方法将接近每设置/分配一个变量调用一次。另一方面,如果它几乎总是返回一个基元值,那么每个赋值将接近2个调用,在这种情况下,也许您应该重构代码。

答案已经给了我

简言之:

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()
函数,但我认为这无论如何都是有用的。

您可以将
SomeMethod
更改为
sub-SomeMethod(变量为var):设置var=xxx…
并使用
SomeMethod v
@AlexK指定给v byref样式。:巧合的是,虽然出于其他原因(我需要类似于.NET的TryParse/TryGet模式的东西,所以我让SomeMethod返回一个布尔值并使用ByRef作为变量),但这正是我最终使用的方法。我仍然需要在SomeMethod中进行丑陋的IsObject检查,这让我有点困扰,但我想VBA中没有
LetSet v=…
语句。另一种语法是将
LetSet
视为(参数化)属性,允许您执行
LetSet(v)=SomeMethod()
-例如,对于代码,该方法可以返回特定对象。您需要将方法调用强制转换为一个变量以避免崩溃:VariantCopy v,CVar(SomeMethod())@CristianBuse真的吗?你能举一个这样一个物体的例子吗?我以前没有经历过这种情况,但我想知道是什么导致了崩溃:)为没有正确测试而道歉。对于VariantCopy API的声明,我使用了“Function”而不是“Sub”,并且我还缺少了损害API的返回类型(只要)。使用您的声明可以很好地工作。
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