0

I have this code that consists of a function and subroutine that uses that function. Function takes a column as an argument and alters each of that column cell's content.

Function testFunc(aCol As Range)
    Dim i As Integer
    i = 0
    For Each cell In aCol
        cell.Value = i
        i = i + 1
    Next
End Function


Sub testSub()
    Dim aRange As Range
    Set aRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A16")

    testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16"))   '<--- this works fine
    testFunc (aRange)    '<---- error
End Sub

The Runtime error '424' Object required"| arises on linetestFunc (aRange)`. I'm not sure what's wrong, I assign variable through 'Set' and it's supposed to work fine.

Also, I tried passing the range directly to the function, without storing it in the variable first and it workes perfectly. Why?

2
  • 1
    Don't wrap the parameter in brackets. have a look here: stackoverflow.com/questions/5413765/… Commented Jul 5, 2017 at 12:53
  • @sous2817, Ok. But it doesn't seem to explain why in the first case it workes fine (see edit). Commented Jul 5, 2017 at 13:06

2 Answers 2

3

Parentheses around an expression are causing the expression to be evaluated as a value and passed ByVal to the procedure invoked.

testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16"))   '<--- this works fine

No, it doesn't work fine, not with aCol As Range in the signature of testFunc - for the exact same reason as the line underneath it. A Range object has a default member that points to its .Value. When you do this:

DoSomething (SomeRange)

What's really happening is this:

DoSomething (SomeRange.Value)

The parentheses are telling VBA to evaluate the expression as a value, before passing the result by value (ByVal) of that expression to the procedure being invoked, regardless of the implicit ByRef modifier in the procedure's signature.

Problem is, given a 16-cell Range, Range.Value is a 2D array - and given a single-cell Range, Range.Value is a Variant that contains that cell's value, and that can't possibly be coerced into a Range object instance, so it's impossible that the code you have here "works fine": it's an object required error, because the procedure is expecting a Range and you're giving it a Variant that contains a 2D array.


Why your "works fine" code isn't really working

The only way testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16")) could possibly work, is if testFunc had this signature (which, I suspect it did at one point before you ultimately asked this question):

Function testFunc(aCol)

Or the explicit equivalent:

Public Function testFunc(ByRef aCol As Variant) As Variant

And because a 2D array can be iterated with a For Each loop (albeit very inefficiently), and it's possible that the entire A1:A16 range contains values that can be coerced into valid Integer values, "works fine" indeed... except you're updating an in-memory copy of the data, not the cells themselves - so it wasn't quite "works fine", was more like "doesn't crash".

Here's a MCVE:

Private Function DoSomething(ByRef foo As Range)
End Function

Public Sub Test()
    DoSomething (Range("A1").Value)
End Sub

Calling Test throws run-time error 424 "Object Required".

Public Function DoSomething(ByRef foo As Range)
End Function

Public Sub Test()
    DoSomething (Range("A1:A100").Value)
End Sub

Calling Test throws the same run-time error 424 "Object Required".

Now remove the As Range declaration:

Public Function DoSomething(ByRef foo As Variant)
    Debug.Print TypeName(foo)
End Function

Public Sub Test()
    DoSomething (Range("A1:A100").Value)
End Sub

Invoking Test prints Variant() to the immediate pane (Ctrl+G). Now make the range contain just one single cell:

Public Function DoSomething(ByRef foo As Variant)
    Debug.Print TypeName(foo)
End Function

Public Sub Test()
    DoSomething (Range("A1").Value)
End Sub

Invoking Test prints whatever data type the value of cell A1 holds. If it's text, it prints String. If it's a number, it prints Double. If it's a date, it prints Date. If it's a cell error (e.g. #NA, #VALUE!, etc.), it prints Error.

Note that the results are identical for all of the above, whether you specify .Value explicitly or not, because omitting .Value boils down to implicitly specifying it.

Also note that passing an array ByVal is illegal. What's being passed to the function here is a copy of a pointer to the beginning of the array - the Variant array isn't storing the array itself, just a pointer to its actual location: that's why & how you can legally pass a variant array by value.


Why the variable is blowing up too

The 2nd line is throwing the same error, for the same reason:

testFunc (aRange)    '<---- error

You're implicitly doing this:

testFunc aRange.Value

Because that's what the parentheses do: they tell VBA to evaluate the expression as a value, and pass it as such.

So you're passing a 2D variant array to a procedure that's expecting a Range.

This is different, and correct:

testFunc aRange

Note the lack of parentheses: now you're passing the Range object reference to a procedure that accepts a Range object reference, ByRef (although that modifier is implicit and a more semantically correct way to do it would be to pass the object reference ByVal).


So how do I tell when the arguments are being evaluated as a value?

When you invoke a Function procedure, you're normally interested in its return value:

result = MsgBox("Confirm?", vbYesNo)

The VBA automatically positions the opening parenthesis immediately after the function's identifier - no whitespace.

When you invoke a Function procedure without capturing its return value, you must omit the parentheses:

MsgBox "Confirm?", vbYesNo

Note that this would be a compile error:

MsgBox ("Confirm?", vbYesNo)

Because the expression ("Confirm?", vbYesNo) cannot be evaluated as a value and passed as such to the first parameter of the procedure.

That's easy though - MsgBox has multiple parameters. People get confused when there's only one parameter - because then this looks legal:

testFunc (aRange)

Notice the whitespace: it shouldn't be there. That's the VBE telling you "these parentheses are NOT part of the function call, they're part of the argument you're going pass to that function, and if whatever is in these parentheses can't be evaluated as a value at run-time, expect trouble".


If your testFunc procedure isn't returning a value, then it shouldn't be a Function in the first place - it should be a Sub procedure. Functions take input and return a value, Subs do something, which usually entails side-effects, e.g. actual cell values being modified.

Sign up to request clarification or add additional context in comments.

1 Comment

this answer is really great :)
0

Your code woubl to be lik this.

Sub testFunc(aCol As Range)
    Dim i As Integer
    i = 0
    For Each cell In aCol
        cell.Value = i
        i = i + 1
    Next
End Sub


Sub testSub()
    Dim aRange As Range
    Set aRange = ThisWorkbook.Sheets("Sheet1").Range("A1:A16")

    testFunc aRange    '<---- error
End Sub

4 Comments

Excuse me, did you change anything?
Thank you, that worked. However it-s just a simplified example of a bigger program, I would really rather use function, why isn't this possible here and if there some other way arounf it?
@Ans: you can use function.
@Ans A Function should return something, a Sub should be a procedure with no return value. Theoretically there's nothing stopping you from using them interchangeably - but it's bad practice and confusing to do so in VBA

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.