函数与子函数(ByRef)

VBa*_*008 5 excel vba

关于

  • 这个问题不是关于何时使用 aFunction或 a ,或者和Sub之间的区别(尽管一些见解是不可避免的)。ByRefByVal
  • 它涉及“通常”使用 a 解决的场景Function,但也可以选择使用“修改”意义上的Subusing来解决。ByRef

代码

考虑以下函数:

' Returns the worksheet (object) with a specified name in a specified workbook (object).
Function getWsF(wb As Workbook, _
                ByVal wsName As String) _
         As Worksheet
    ' 'getWsF' is 'Nothing' by default.
    ' Try to define worksheet.
    On Error Resume Next
    Set getWsF = wb.Worksheets(wsName)
End Function
Run Code Online (Sandbox Code Playgroud)

您可以像下面这样使用它:

' Writes the name of a specified worksheet, if it exists, to the `Immediate` window...
Sub testFunction()
    
    Const wsName As String = "Sheet1"
    Dim wb As Workbook
    Set wb = ThisWorkbook ' The workbook containing this code.
    
    ' Define worksheet.
    Dim ws As Worksheet
    Set ws = getWsF(wb, wsName)
    
    ' Test if worksheet exists.
    If Not ws Is Nothing Then
        Debug.Print "The worksheet name is '" & ws.Name & "'."
    Else
        Debug.Print "Worksheet '" & wsName & "' doesn't exist in workbook '" _
                  & wb.Name & "'."
    End If

End Sub
Run Code Online (Sandbox Code Playgroud)

但您也可以按以下方式编写每个过程:

' Although 'ByRef' is not necessary, I'm using it to indicate that whatever
' its variable is referring to in another procedure (in this case
' a worksheet object), is going to be modified (possibly written to
' for other datatypes).
Sub getWsS(ByRef Sheet As Worksheet, _
           wb As Workbook, _
           ByVal wsName As String)
    ' 'Sheet' could be 'Nothing' or an existing worksheet. You could omit
    ' the following line if you plan to use the procedure immediately
    ' after declaring the worksheet object, but I would consider it
    ' as too risky. Therefore:
    ' 'Reinitialize' worksheet variable.
    Set Sheet = Nothing
    ' Try to define worksheet.
    On Error Resume Next
    Set Sheet = wb.Worksheets(wsName)
End Sub

' Writes the name of a specified worksheet, if it exists, to the `Immediate` window...
Sub testSub()
    
    Const wsName As String = "Sheet1"
    Dim wb As Workbook
    Set wb = ThisWorkbook ' The workbook containing this code.
    
    ' Define worksheet.
    Dim ws As Worksheet
    getWsS ws, wb, wsName
    
    ' Test if worksheet exists.
    If Not ws Is Nothing Then
        Debug.Print "The worksheet name is '" & ws.Name & "'."
    Else
        Debug.Print "Worksheet '" & wsName & "' doesn't exist in workbook '" _
                  & wb.Name & "'."
    End If

End Sub
Run Code Online (Sandbox Code Playgroud)

并排

程序

Function getWsF(wb As Workbook, _       Sub getWsS(ByRef Sheet As Worksheet, _
                wsName As String) _                wb As Workbook, _
         As Worksheet                              wsName As String)
                                          Set Sheet = Nothing
  On Error Resume Next                    On Error Resume Next
  Set getWsF = wb.Worksheets(wsName)      Set Sheet = wb.Worksheets(wsName)
End Function                            End Sub
Run Code Online (Sandbox Code Playgroud)

用途(相关)

  ' Define worksheet.                     ' Define worksheet.
  Dim ws As Worksheet                     Dim ws As Worksheet
  Set ws = getWsF(wb, wsName)             getWsS ws, wb, wsName
Run Code Online (Sandbox Code Playgroud)

问题

  • 第二种解决方案可行吗?
  • 我正在寻找对两个相关程序的作用的正确描述,以及在常见实践、可读性、效率、陷阱方面的一些见解......

Cri*_*use 4

对于你的情况,我会使用这种Function方法,原因如下:

  1. 我可以使用函数的结果而不存储返回变量:
With getWsF(ThisWorkbook, "Sheet1")
    '...
End With
Run Code Online (Sandbox Code Playgroud)

显然,我需要确保它永远不会返回Nothing或有一些错误处理。

或者

DoSomething getWsF(ThisWorkbook, "Sheet1")
Run Code Online (Sandbox Code Playgroud)

其中 DoSomething 是一个需要工作表/无的方法

  1. 正如 @TimWilliams 在评论中提到的,如果您不期望多个返回值,那么这是“预期”的方法。一个公认的约定是,如果方法没有返回值,则它应该是Sub. 如果它有一个返回值,那么它应该是一个Function. 如果它返回多个值,那么它也应该是Functionand:

    • 您可以使用类或类型将它们打包为一个结果

    • 或者,该函数返回一个主值作为结果,其余返回值通过引用(有关示例,请参阅 @UnhandledException 的答案)。

  2. 如果您需要调用该方法,Application.Run那么Function是安全的。使用 aSub通常会导致自动化错误或代码在执行方法后停止运行。是否需要使用 Function 的结果并不重要,Sub如果您不想出现严重错误,请不要使用 Application.Run 调用 a 。当然,为了避免 Application.Run 出现问题,您可以拥有一个不分配返回值但仍返回 Worksheet ByRef 的 Function,但这对读者来说太令人困惑了。

编辑#1

忘记提及Application.Run从不同文档调用方法时会发生自动化错误(对于 Excel - 不同的工作簿)

编辑#2

在本节中,我将尝试解决您的问题的正确描述方面,而不是进行初级解释高级解释,而是综合解释。

Suba和 a之间的区别Function

ASub只是一个函数,函数执行后不返回值。在许多语言中,这样的函数称为Void Function

其含义是 aSub只是一个独立的语句。不能从表达式内部调用它。您只能使用以下之一来调用它:

  • MySub [argsList]
  • Call MySub([argsList])

另一方面,aFunction可以在语句中使用,例如:

  • 其他方法的参数,例如DoSomething MyFunction(...)Debug.Print MyFunction(...)
  • 作业例如x = MyFunction(...)
  • With块例如With MyFunction(...)
  • 方法链接例如MyFunction(...).DoSomething

上面提到的约定:

一个公认的约定是,如果方法没有返回值,则它应该是Sub. 如果它有一个返回值那么它应该是Function

当我们根据定义了解 aSub执行某些操作并且 aFunction返回单个值时,情况就变得非常清楚了。

aSub和 a之间的相似性Function

返回值函数(Function在 VBA 中)和 void 函数(Sub在 VBA 中)都接收值作为参数。在VBA中,可以通过ByRef参数返回结果。并非所有语言都支持 ByRef 参数(例如 Java - 例如修改对象的成员除外)。

请注意,如果在源平台中滥用 ByRef 方法,则将代码从支持 ByRef 的平台移植到另一个不支持 ByRef 的平台可能会非常耗时。

ByValByRef参数之间的差异

按值传递(ByVal):

  • 为新变量分配新的内存空间,该变量将属于被调用方法的本地范围
  • 原始变量的内容被复制到新变量的新分配空间中(对于对象,则复制接口虚拟表的地址)
  • 无论方法做什么,原始变量的内容都不会改变
  • 它更安全,因为程序员不需要记住/关心程序的其他部分(特别是调用方法)

通过引用传递(ByRef):

  • 创建了一个新变量,但没有分配新的内存空间。相反,新变量指向从调用方法传递的原始变量所占用的内存空间。请注意,对于对象,原始变量将被完全传递(不会创建新变量),除非接口不同(将集合作为对象类型参数传递 - 对象代表 IDispatch),但这是本答案范围之外的讨论。另请注意,如果参数声明为 Variant,则会发生更复杂的操作以方便重定向
  • 现在可以远程更改原始变量的内容,因为原始变量和新创建的变量都指向相同的内存空间
  • 它被认为更有效,因为没有分配新的内存,但这会带来复杂性增加的缺点

所提出方法的比较

现在我们对差异有了一些了解,我们可以看看所提出的两种方法。让我们从以下开始Sub

Sub getWsS(ByRef Sheet As Worksheet, wb As Workbook, ByVal wsName As String)
    Set Sheet = Nothing
    On Error Resume Next
    Set Sheet = wb.Worksheets(wsName)
End Sub
Run Code Online (Sandbox Code Playgroud)

首先,On Error GoTo 0在 之前应该有一个语句End Sub,因为否则错误 9 会沿着调用链向上传播(如果未找到工作表),并且在 getWsS 方法返回很久之后会影响其他方法内的逻辑。

方法名称以动词“get”开头,这意味着该方法返回某些内容,但方法声明是 a Sub,根据定义,它更像是Do Something而不是Return Something。可读性肯定会受到影响。

需要一个额外的 ByRef 参数来返回单个结果。影响:

  • 它影响可读性
  • 它需要在调用方法中声明变量
  • 结果不能在调用方法内的其他表达式中链接/使用
  • 它需要额外的行Set Sheet = Nothing以确保原始变量不保留以前的内容

现在,让我们看看该Function方法:

Function getWsF(wb As Workbook, ByVal wsName As String) As Worksheet
    On Error Resume Next
    Set getWsF = wb.Worksheets(wsName)
End Function
Run Code Online (Sandbox Code Playgroud)

和以前一样,前面应该有一个On Error GoTo 0语句End Function,否则错误 9 会沿着调用链向上传播。此外,工作簿可以通过 ByVal 作为最佳实践。

明显差异:

  • getSomething 这个名称非常适合返回 Something 的函数。可读性远优于Sub同行
  • 代码的读者/维护者只需查看返回类型即可立即知道该函数返回一个工作表(而不是查看 ByRef 参数列表并找出哪个是返回变量)
  • 结果可以在表达式中链接/使用
  • 不需要额外的代码行,默认返回值已经是Nothing
  • 使用最广泛接受的约定

我使用过CTimer,在我的 x64 机器上,该Sub方法运行速度更快,当运行该方法一百万次时,大约需要 20 毫秒。这些微小的效率提升值得牺牲可读性和使用灵活性吗?这是只有代码库的维护者才能决定的事情。