表达式周围的括号导致表达式被评估作为一个值并将ByVal传递给调用的过程。
testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16")) '<--- this works fine
不,它不能正常工作,在testFunc 的签名中没有aCol As Range - 原因与它下面的行完全相同。 Range 对象有一个 默认成员 指向它的 .Value。当你这样做时:
DoSomething (SomeRange)
真正发生的事情是这样的:
DoSomething (SomeRange.Value)
括号告诉 VBA 将表达式评估为一个值,在传递结果 按值 (ByVal)被调用过程的表达式,不管过程签名中隐含的ByRef 修饰符。
问题是,给定一个 16 单元格 Range,Range.Value 是一个二维数组 - 给定一个单单元格 Range,Range.Value 是一个包含该单元格值的 Variant,这不可能被强制转换为Range 对象实例,因此您在此处的代码不可能“正常工作”:这是一个object required错误,因为该过程期待一个Range,而你给它一个包含二维数组的Variant。
为什么你的“工作正常”的代码不能真正工作
testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16")) 可能起作用的唯一方法是,testFunc 是否有这个签名(我怀疑它在你最终问这个问题之前的某个时候做过):
Function testFunc(aCol)
或显式等价物:
Public Function testFunc(ByRef aCol As Variant) As Variant
因为二维数组可以用For Each循环(albeit very inefficiently)进行迭代,并且整个A1:A16范围可能包含可以 被强制转换为有效的 Integer 值,确实“工作正常”...除了您要更新内存中的数据副本,而不是单元格本身 - 所以它并不完全“工作正常”,更像是“不会崩溃”。
这是一个 MCVE:
Private Function DoSomething(ByRef foo As Range)
End Function
Public Sub Test()
DoSomething (Range("A1").Value)
End Sub
调用 Test 会引发运行时错误 424“需要对象”。
Public Function DoSomething(ByRef foo As Range)
End Function
Public Sub Test()
DoSomething (Range("A1:A100").Value)
End Sub
调用 Test 会引发相同的运行时错误 424“需要对象”。
现在删除As Range 声明:
Public Function DoSomething(ByRef foo As Variant)
Debug.Print TypeName(foo)
End Function
Public Sub Test()
DoSomething (Range("A1:A100").Value)
End Sub
调用Test 会将Variant() 打印到立即窗格 (Ctrl+G)。现在使范围只包含一个单元格:
Public Function DoSomething(ByRef foo As Variant)
Debug.Print TypeName(foo)
End Function
Public Sub Test()
DoSomething (Range("A1").Value)
End Sub
调用Test 会打印单元格A1 的值所持有的任何数据类型。如果是文本,则打印String。如果是数字,则打印 Double。如果是日期,则打印 Date。如果是单元格错误(例如#NA、#VALUE! 等),则会打印出Error。
请注意,无论您是否显式指定.Value,上述所有结果都是相同的,因为省略.Value 归结为隐式指定它。
另请注意,传递数组ByVal 是非法的。此处传递给函数的是指向数组开头的指针的副本 - Variant 数组不存储数组本身,只是一个指针到它的实际位置:这就是为什么以及如何合法地按值传递变体数组。
为什么变量也爆炸了
出于同样的原因,第二行抛出了同样的错误:
testFunc (aRange) '<---- error
你隐含地这样做了:
testFunc aRange.Value
因为这就是括号的作用:它们告诉 VBA 将表达式作为一个值进行评估,然后将其传递。
因此,您将 2D 变量数组传递给需要 Range 的过程。
这是不同的,而且是正确的:
testFunc aRange
请注意缺少括号:现在您将Range 对象引用传递给接受Range 对象引用ByRef 的过程(尽管该修饰符是隐式的并且在语义上更正确将传递对象引用ByVal)。
那么我如何判断参数何时被评估为一个值?
当您调用Function 过程时,您通常对它的返回值感兴趣:
result = MsgBox("Confirm?", vbYesNo)
VBA 自动将左括号紧跟在函数标识符之后 - 没有空格。
当您调用Function 过程而不捕获其返回值时,您必须省略括号:
MsgBox "Confirm?", vbYesNo
请注意,这将是一个编译错误:
MsgBox ("Confirm?", vbYesNo)
因为表达式("Confirm?", vbYesNo) 不能作为一个值被计算并传递给过程的第一个参数。
这很容易 - MsgBox 有多个参数。当只有一个参数时,人们会感到困惑 - 因为这样 看起来 合法:
testFunc (aRange)
注意空格:它不应该在那里。这就是 VBE 告诉你 “这些括号不是函数调用的一部分,它们是你要传递给该函数的参数的一部分,如果这些括号中的任何内容都不能作为一个值进行评估在运行时,期待麻烦”。
如果您的testFunc 过程没有返回值,那么它首先不应该是Function - 它应该是Sub 过程。函数接受输入并返回一个值,Subs 做某事,这通常会带来副作用,例如正在修改的实际单元格值。