ParamArray help needed

I have wrapper methods for database calls, so I can handle and more particularly log errors, and not have to pad out the main logic for every database call. So, for ExecuteSQL, I might have this:

dbexec (Extends dbh as sqlitedatabase, sql as string, ParamArray args as Variant) as Boolean
try
  dbh.ExecuteSQL (sql, args)
  Return True                      // Worked fine, just return
Catch e as DatabaseException
  dblogargs (args)                 // Log argument values here
end try

.
What I want to do in dblogargs is to pick out the args which are integers, and log only those, so I might end up with a message in the log file something like:

arg0=27, arg3=5, arg4=7

(in this picture, I skip arg1 and arg2 because they are strings)

Where I’m getting into trouble is getting the type of each element of args, the ParamArray. If I look at VarType(arg) or arg.Type(), then each arg appears to be type 9 (Object). And if I have an arg that I know to be an integer, then something like:

Var  i as Integer = arg.IntegerValue

just gives me a TypeMismatchException. How do I get at the integer that must be hidden in there somehow?

You want to access the elements in the arg array, not refer to the array as a whole:

Var i as Integer = arg(INDEX).IntegerValue

Mmm, what I actually have is:

msg3 = "   "
i = 0

for each arg in args
  
  nxtype = arg.Type () - 4096
  //if  (nxtype<>Variant.TypeInt32 and nxtype<>Variant.TypeInt64)  then Continue
  
  if  (i>0)  then msg3 = msg3 + ", "
  
  nxtval = arg.IntegerValue
  msg3 = msg3 + "arg" + i.ToString + "=" + nxtval.ToString
  i = i + 1
  
next

Edit: of course, the i.ToString is wrong but I’ll sort that out later.

Don’t you want

for each arg in args
  select case VarType( arg )
     case Variant.TypeInt32, Variant.TypeInt64
        // Do your logging output
  end select
next

Yes, you could do it with an If, but this scales nicely if you also want to deal with other types (for example double or singles). There’s no need for “- 4096” as an element of the array is never going to be an array itself.

That’s arguably tidier, but:

  1. I tried VarType(arg) and arg.Type(), which both returned 4105 (which is 4096+9)

  2. arg seems to be a variant containing an object which may itself contain the integer I’m after. How to get at it is the issue.

You’d certainly think so - I did, and was bemused to get the value back that I did.

Some slight progress - The method to use is:

nxtype = arg.ArrayElementType ()

No need for -4096. But the Value I get back is still 9 (object).

So - modified rapture.

Do you not need to declare the type for the iterator, Ă  la

for each arg as Variant in args

surprised that for each arg in args would even compile.

arg is declared as Variant at the top of the method.

1 Like
Public Sub TheMethod(Paramarray Args As Variant)
  Dim arg As Variant, s As String
  
  For Each arg In Args
    s = s + Str(arg.Type) + EndOfLine
  Next
  
  MessageBox s
End Sub

TheMethod(1, 2, "foo", "bar")

Screenshot 2024-02-14 at 1.30.18 PM

2 Likes

Fascinating. That could be considered a bug. Good catch with ArrayElementType. I still like the case statement.

Same results as Julia, everything ok.

Thanks for checking that. The thing is, I’m passing the ParamArray (args) down to another method, which your working examples didn’t do, thus not quite mirroring my case. So I wondered about that. Turns out, if you do that, that in that next method down (dblogargs, see my OP), you no longer declare args as a ParamArray - you just declare it: args() as Variant - and all the issues I was having went away.

So, what I have is:

dbexec (Extends dbh as sqlitedatabase, sql as string, ParamArray args as Variant) as Boolean
try
  dbh.ExecuteSQL (sql, args)
  Return True                      // Worked fine, just return
Catch e as DatabaseException
  dblogargs (args)                 // Log argument values here
end try

.
and:

dblogargs (args() as Variant)
// code to log the args.

Thanks all.

1 Like

Yup, a ParamArray is not an Array

If you are passing a param array into another function as a param array then I can see why you get an array, as the whole array would likely get passed into a single element of the second param array.

You are boxing the object, you need to unbox it to use it as

Public Function unboxPA(boxedPA As Variant) As String

  Var args() As Variant = boxedPA
  var msg As String
  
  for each arg as variant in args
    msg = msg + arg.StringValue + " = type(" + arg.Type.ToString + ")" + EndOfLine
  Next
  
  Return msg
  
End Function


Public Function pa(paramarray args() As Variant) As String
  
  Return unboxPA(args)
  
  // MessageBox pa("aa", 1, 2)
  
End Function

image

Yes. The first use of ParamArray gathers up the list of parameters and puts them into an array, from which they can be taken and analysed just as @Julia_Truchsess and @Rick_Araujo showed. (Seeing that was most helpful). What I was doing was then passing that array through to the next level. But using ParamArray there, caused the one item (the array of parameters) to become the one item in an encompassing array, which is where my troubles started. Perhaps if I’d done:

Var myArray() as variant = args(0)

then all may have worked earlier. But simply omitting the second level ParamArray keyword is better.

Unbox it as I showed and use it as always. Just it.

A common paradigm is to have a method that does all the work and accepts an array as its parrameter. Then you override it with a method of the same name that accepts a ParamArray as its parameter. All it does is call the first version.

Sub someMethod(a() as variant)
   // do the work
End Sub

Sub someMethod(ParamArray pa() as variant)
   someMethod(pa)
End Sub
1 Like

Solved directly at the parameter passage. Tim’s direct approach more easy to see and understand:


Public Function receive_pa_args(args() As Variant) As String
  
  var msg As String
  
  for each arg as variant in args
    msg = msg + arg.StringValue + " = type(" + arg.Type.ToString + ")" + EndOfLine
  Next
  
  Return msg
  
End Function


Public Function pa(paramarray args() As Variant) As String
  
  Return receive_pa_args(args)
  
End Function


// Use like: 

MessageBox pa("aa", 1, 2)