我有这段代码,它由一个函数和使用该函数的子例程组成。函数将列作为参数,并更改该列单元格的每个内容。
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
Runtime error '424' Object required"| arises on line
testFunc (aRange)'。我不确定出了什么问题,我通过"Set"分配变量,它应该可以正常工作。
此外,我尝试将范围直接传递给函数,而无需先将其存储在变量中,它运行良好。为什么?
表达式周围的括号会导致表达式被计算为值,并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
是一个 2D 数组 - 并且给定一个单单元格Range
,Range.Value
是一个包含该单元格值的Variant
,并且不可能被强制到Range
对象实例中,因此您在此处的代码不可能"正常工作":这是一个对象需要错误, 因为该过程需要Range
,而您正在为其提供包含 2D 数组的Variant
。
为什么你的"工作正常"代码实际上不起作用
testFunc (ThisWorkbook.Sheets("Sheet2").Range("A1:A16"))
可能工作的唯一方法是,如果testFunc
有这个签名(我怀疑在你最终问这个问题之前,它曾经有过
Function testFunc(aCol)
或显式等效项:
Public Function testFunc(ByRef aCol As Variant) As Variant
而且由于 2D 数组可以使用For Each
循环进行迭代(尽管效率非常低),并且整个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
数组不存储数组本身,只是指向其实际位置的指针:这就是为什么以及如何合法地按值传递变体数组。
为什么变量也爆炸了
出于同样的原因,第 2 行抛出相同的错误:
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做一些事情,这通常会带来副作用,例如修改实际的单元格值。
你的代码很像这个。
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