【问题标题】:Do I need to pass a worksheet as ByRef or ByVal?我需要将工作表作为 ByRef 或 ByVal 传递吗?
【发布时间】:2026-02-08 10:10:01
【问题描述】:

我从一个较大的块中分解出一些代码,需要将工作表传递给它...

我没有为工作表分配任何新值,但我正在更改该工作表的分页符设置。我需要将它作为 ByRef 传递,还是 ByVal 足够好?

Private Sub SetPageBreaks(ByRef wsReport As Worksheet)
Dim ZoomNum As Integer

  wsReport.Activate
  ActiveWindow.View = xlPageBreakPreview
  ActiveSheet.ResetAllPageBreaks
  ZoomNum = 85
  With ActiveSheet
    Select Case wsReport.Name
      Case "Compare"
        Set .VPageBreaks(1).Location = Range("AI1")
        ZoomNum = 70
      Case "GM"
        .VPageBreaks.Add before:=Range("X1")
      Case "Drift"
        .VPageBreaks.Add before:=Range("T1")
      Case Else
        .VPageBreaks.Add before:=Range("U1")
    End Select
  End With
  ActiveWindow.View = xlNormalView
  ActiveWindow.Zoom = ZoomNum

End Sub

【问题讨论】:

标签: vba excel pass-by-reference


【解决方案1】:

两者都可以,但对于语义正确的代码,最好按值传递 (ByVal)。

当您按值传递对象变量时,您将指针副本传递给对象。

所以程序使用的同一个对象(即更改的属性值被调用者看到),除了它不允许Set指向其他东西的指针——它可以,但它会在自己的副本上这样做,因此调用者不会受到影响。

Public Sub DoSomething()
    Dim target As Worksheet
    Set target = ActiveSheet
    Debug.Print ObjPtr(target)
    DoSomethingElse target
    Debug.Print ObjPtr(target)
End Sub

Private Sub DoSomethingElse(ByVal target As Worksheet)
    Debug.Print ObjPtr(target)
    Set target = Worksheets("Sheet12")
    Debug.Print ObjPtr(target)
    'in DoSomething, target still refers to the ActiveSheet
End Sub

另一方面...

Public Sub DoSomething()
    Dim target As Worksheet
    Set target = ActiveSheet
    Debug.Print ObjPtr(target)
    DoSomethingElse target
    Debug.Print ObjPtr(target)
End Sub

Private Sub DoSomethingElse(ByRef target As Worksheet)
    Debug.Print ObjPtr(target)
    Set target = Worksheets("Sheet12")
    Debug.Print ObjPtr(target)
    'in DoSomething, target now refers to Worksheets("Sheet12")
End Sub

一般来说,参数应该按值传递。 ByRef 是默认值,这只是一个不幸的语言怪癖(VB.NET 已修复)。

对于非对象变量也是如此:

Public Sub DoSomething()
    Dim foo As Long
    foo = 42
    DoSomethingElse foo
End Sub

Private Sub DoSomethingElse(ByVal foo As Long)
    foo = 12
    'in DoSomething, foo is still 42
End Sub

还有……

Public Sub DoSomething()
    Dim foo As Long
    foo = 42
    DoSomethingElse foo
End Sub

Private Sub DoSomethingElse(ByRef foo As Long)
    foo = 12
    'in DoSomething, foo is now 12
End Sub

如果一个变量通过引用传递,但从未在过程体中重新分配,那么它可以通过值传递。

如果一个变量是通过引用传递的,并在一个过程的主体中重新分配它,那么这个过程可能会被写成Function,实际上返回修改后的值。

如果一个变量是按值传递的,并且在过程体中被重新赋值,那么调用者就不会看到这些变化——这会使代码变得可疑;如果一个过程需要重新分配 ByVal 参数值,如果它定义自己的局部变量并分配 that 而不是 ByVal 参数,则代码的意图会变得更加清晰:

Public Sub DoSomething()
    Dim foo As Long
    foo = 42
    DoSomethingElse foo
End Sub

Private Sub DoSomethingElse(ByVal foo As Long)
    Dim bar As Long
    bar = foo
    '...
    bar = 12
    '...
End Sub

这些都是Rubberduck 中的所有实际代码检查,作为我大量参与的 VBE 插件,它可以分析您的代码并查看这些内容:

参数是按值传递的,但被分配了一个新的值/引用。如果调用者不应该知道新值,请考虑制作本地副本。如果调用者应该看到新值,则应该改为通过 ByRef 传递参数,并且您有一个错误。

http://rubberduckvba.com/Inspections/Details/AssignedByValParameterInspection

只有一个通过引用传递的参数在过程退出之前被分配了一个新值/引用的过程正在使用 ByRef 参数作为返回值:考虑将其改为函数。

http://rubberduckvba.com/Inspections/Details/ProcedureCanBeWrittenAsFunctionInspection

通过引用传递且未分配新值/引用的参数可以改为按值传递。

http://rubberduckvba.com/Inspections/Details/ParameterCanBeByValInspection

这里是wsReport的情况:

【讨论】:

  • 我使用 Rubberduck,这就是促使我思考这个问题的原因。虽然从未为工作表变量分配新值(ByVal 很好),但对工作表进行更改(更改通常需要 ByRef)这一事实让我想知道 ByVal 是否是传递对象的最佳实践。跨度>
  • 嗨,马修。您是说对于 Worksheets 和任何其他 Office 对象,实际发生的是对本身指向 Worksheet 的对象的引用?所以本质上我们传递的ByVal MyVar as Worksheet 不是MyVar 指向的Worksheet 对象的副本,而是中间对象的副本?所以MyVar 指向那个中间对象,它本身指向工作表?
  • @Ama 没有 中间对象 .. 我的意思是对象和指向该对象的 指针 之间存在差异 -被复制或传递的是指针,而不是对象。
  • Shoot 我一直错误地认为对象已被复制,并且 ByVal 带有一个指向该新对象的指针。所以实际上 ByVal 只防止修改数据类型......这些年来!这说明了您多久会修改子/函数中的对象形式,将相同的对象作为参数.. 必须运行一些测试.. 谢谢!
  • ^^ 这篇文章对我来说也是一个灯泡时刻 +