4

If I have a function which may return an object or a primitive type - within it I can do the following to handle those two cases:

Function Result() As Variant 'may be object or not
    '... get item - the return value
    If IsObject(item) Then
        Set Result = item
    Else
        Result = item
    End If
End Function

However, how can I do the same test for the variable where Result is stored without running the function two times? Eg

Dim myResult As Variant
If IsObject(Result) Then 'test return type of function
    Set myResult = Result
Else
    myResult = Result
End If

As

myResult = Result 'fails if Result returns object
Set myResult = Result 'fails if Result returns non-object

I am trying to write a series of objects/non objects to an array of variant type

Community
  • 1
  • 1
Greedo
  • 4,967
  • 2
  • 30
  • 78
  • I'm curious about the specifics where you wouldn't know what was being returned. In theory, you've declared a var to catch the return so when wouldn't you know what was being returned? –  Jun 28 '18 at 10:46
  • 1
    @Jeeped I'm writing a buffer class which takes an array/collection of stuff. It loops through the stuff adding it to an internal store. Once the internal store reaches a certain capacity an event is raised and the stuff in the store is written to an array which can be read. Since the internal store is being constantly written to, I can't expose that, so I need to clone it somehow, which requires writing items of unknown type to a variant array. – Greedo Jun 28 '18 at 10:52
  • Possible duplicate of [How can I assign a Variant to a Variant in VBA?](https://stackoverflow.com/questions/35750449/how-can-i-assign-a-variant-to-a-variant-in-vba) – Greedo Nov 03 '19 at 18:37

3 Answers3

3

Well one possible solution is to write to the variable directly, by passing it ByRef. In a standard module:

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

called like

Dim myResult As Variant
LetSet(myResult) = 123                'myResult = 123
LetSet(myResult) = New Collection     'Set myResult = New Collection
Greedo
  • 4,967
  • 2
  • 30
  • 78
  • 2
    [Review of this approach](https://codereview.stackexchange.com/questions/231790/assign-a-variant-to-a-variant) – Greedo Nov 03 '19 at 18:39
1

As an example a snippet from a class, that deals with calling class functions dynamically. It's work in progress for prototyping if can implement some basic VBA Reflection, based off RubberDuck Reflection API. As I'm dynamically calling class and module functions there's no reliable way to know in advance if a primitive or object type will be returned from a function call.

Note I haven't tested the code yet and will need some error handling added to the InvokeMember function.

Greedo's solution is similar expect the function result is a separate parameter by reference, that way can put in the function call eg. CallByName as the second parameter. The first parameter is the variable storing the result called by reference. The function result parameter y is by reference as an array could result and avoids copying the whole array in memory when passed in.

Public Function InvokeMember(ByVal obj As Object, ParamArray args() As Variant) As Variant
    Assign InvokeMember, CallByName(obj, Me.FullName, Me.CallType, args)
End Function

In a module, code from VBA Extension Library

' Assign x to y regardless of object or primitive
Public Sub Assign(ByRef x as Variant, ByRef y as Variant)

    If IsObject(y) Then
        Set x = y
    Else
        x = y
    End If

End Sub

Thanks for the question it's helped me solve the same issue avoiding calling the same function twice when dealing with whether it's an object, array or primitive data type being returned.

M. Johnstone
  • 72
  • 1
  • 6
  • Unless I'm missing an important point, it would appear this does not solve the OP's problem. The external caller still has to call `InvokeMember()` and store its result somewhere, and that is where the same problem arises - you need to peek in the future to learn if you should use `x = InvokeMember()` or `Set x = InvokeMember()`. – GSerg Dec 25 '18 at 21:34
  • I see; it's `CallByNameOutput` that is important, it should be made `public`, and `InvokeMember` is not relevant and should really be removed from the answer. Then the OP would call it as `Dim myResult As Variant : CallByNameOutput Result(), myResult`. – GSerg Dec 25 '18 at 21:53
  • Sorry just updated to make it simpler with the naming. I guess could do Assign DeclarationTypeClassFunction.InvokeMember(obj, args), varResult. just realized how handy that Assign sub is in a project trying to make sense off. – M. Johnstone Dec 25 '18 at 21:55
  • Now you don't want `y` to be byval to avoid creating copies of arrays. – GSerg Dec 25 '18 at 21:57
  • Yeah a public sub makes more sense as would be widely used. Argh so that's why they had Assign throughout the RubberDuck VBEX project. The answers been infront of me for a few weeks. :) – M. Johnstone Dec 25 '18 at 22:01
  • I need to reverse my call parameters around to work with the Assign sub. :) – M. Johnstone Dec 25 '18 at 22:05
  • Thanks, GSerg Agree that the IvokeMember isn't completely relevant thou does show an example of using the Assign. – M. Johnstone Dec 25 '18 at 22:22
  • Thanks, GSerg I didn't realize that the code from the RubberDuck VBEX cast.bas had the y ByVal thou when I was explaining it should be by reference due to the function could pass out Array and slow things down as arrays are completely copied. – M. Johnstone Dec 27 '18 at 06:51
1

An alternative solution is to use the Array() function and wrap the function or property output in it.
From memory when I was testing my original solution it didn't work for where the value came from a property of a class only functions. This was an issue when using Interfaces or directly using the property and obtaining variant results.
Note: I don't think it will work for functions/properties returning a UDT or fixed string length.

For your example use the following:

Dim myResult As Variant
Dim resultOutput as Variant
resultOutput = Array(Result)
If VBA.IsObject(resultOutput(0)) Then
    set myResult = resultOutput(0)
Else
    myResult = resultOutput(0)
End If
M. Johnstone
  • 72
  • 1
  • 6