excel vba - 返回两个值,传递一个输入参数

标签 excel vba function

情况是这样的:
我需要从一个函数返回两个值:这个函数需要一个输入参数才能工作。

strTitleName:输入参数
sName:输出参数
sScope:输出参数

Function getScenarioName(strTitleName As String, sName As String, sScope As String)
    activateSheet ("Test Scenarios")
    Dim rng1 As Range
    Dim strSearch As String
    strSearch = strTitleName & "*"
    Set rng1 = Range("B:B").Find(strSearch, , xlValues, xlWhole)
    If Not rng1 Is Nothing Then
     'getScenarioName = rng1.Offset(0, 0)
     sName = rng1.Address
     sScope = rng1.Offset(1, 1).Address
    Debug.Print "sName=" & sName
    Debug.Print "sScope=" & sScope
End If

如何在子程序中获取 sName 和 sScope 的值?

最佳答案

这里的概念是 ByRefByVal 参数。

在 VBA 中,除非另有说明,否则参数是通过引用传递的,这是……不幸的默认值:在大多数其他语言中,参数是通过值传递的。

99% 的时间,你不需要传递任何东西 ByRef ,所以 ByVal 是完美的,应该明确指定...... 99% 的时间。

传递参数 ByRef 对于这样的情况很有用,当您需要返回两个或多个值并返回一个封装返回值的类的实例时将是矫枉过正。

请记住,Function 过程总是返回一个值,即使您没有声明返回类型。如果您未能声明返回类型,并且从不分配返回值,则该函数将返回 Variant/Empty ,这会导致一个相当困惑且非惯用的 API。

这里有几个选项:

ByRef 返回参数

假设您的签名如下所示:

Public Function GetScenarioName(ByVal title As String, ByRef outName As String, ByRef outScope As String) As Boolean

现在您可以在函数成功时返回 True,在失败时返回 False(例如,如果 rng1 恰好是 Nothing ),然后分配 outNameoutScope 参数。

因为它们是通过引用传递的,所以调用代码可以看到新值——所以调用者看起来像这样:
Dim scenarioTitle As String
scenarioTitle = "title"

Dim scenarioName As String, scenarioScope As String
If GetScenarioName(scenarioTitle, scenarioName, scenarioScope) Then
    Debug.Print scenarioName, scenarioScope
Else
    Debug.Print "No scenario was found for title '" & scenarioTitle & "'."
End If

发生的情况是函数接收到 scenarioTitle 变量的副本 - 该副本本质上是函数本地的变量:如果您在函数体中重新分配它,调用者不会看到更新的值,原始参数不受影响(这就是为什么 ByVal 是最安全的参数传递方式)。

但是该函数还接收对 scenarioNamescenarioScope 变量的引用——当它分配给它的 outNameoutScope 参数时,该引用所持有的值会相应地更新——调用者可以看到更新的值。

用户定义类型

仍然利用 ByRef 返回值,有时将成员封装在一个有凝聚力的单元中可能是个好主意:VBA 允许您创建用户定义的类型,对于您只需要折腾一堆值的简单情况:
Public Type TScenario
    Title As String
    Name As String
    Scope As String
    '...
End Type
Public Function GetScenarioInfo(ByRef info As TScenario) As Boolean

现在这个函数将类似地工作,除了现在您不再需要在想要添加参数时更改其签名:只需将新成员添加到 TScenario 就可以了!

调用代码将这样做:
Dim result As TScenario
result.Tite = "title"
If GetScenarioInfo(result) Then
    Debug.Print result.Name, result.Scope
Else
    Debug.Print "No scenario was found for title '" & result.Title & "'."
End If

或者,您可以有一个成熟的类模块来封装 ScenarioInfo - 在这种情况下......

成熟的面向对象编程

将你需要的一切封装在它自己的类模块中给你最大的灵 active :现在你的函数可以返回一个对象引用!
Public Function GetScenarioName(ByVal title As String) As ScenrioInfo

现在如果没有找到场景,该函数可以返回Nothing,并且除了输入参数之外不需要任何其他参数:
Dim scenarioTitle As String
scenarioTitle = "title"

Dim result As ScenarioInfo
Set result = GetScenarioInfo(scenarioTitle)
If Not result Is Nothing Then
    Debug.Print result.Name, result.Scope
Else
    Debug.Print "No scenario was found for title '" & scenarioTitle & "'."
End If

这是 IMO 最干净的方法,但确实需要一些样板文件 - 即 ScenarioInfo 类模块。最简单的可能实现将简单地公开读/写公共(public)字段:
Option Explicit
Public Name As String
Public Scope As String

更复杂的实现可能涉及仅公开 IScenarioInfo 成员的 Property Get 接口(interface)、 ScenarioInfoImplements IScenarioInfo 类、 VB_PredeclaredId 属性(这是......隐藏......并且使用公共(public)工厂方法更容易使用 Rubberduck VBIDE 插件处理)这使您可以参数化对象的创建 - 将函数变成如下所示:
If Not rng1 Is Nothing Then
    Set GetScenarioInfo = ScenarioInfo.Create(rng1.Address, rng1.Offset(1,1).Address)
End If

如果这是一种你觉得有趣的方法,你可以在我维护的 Rubberduck News 博客上阅读它。

关于excel vba - 返回两个值,传递一个输入参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55343867/

相关文章:

excel - 如何插入双引号或单引号

excel - 如何在将数据从 Excel 导入 Outlook 时设置固定列宽?

c - 为什么编译器对未使用的函数发出警告?

excel - 将列中所有单元格的格式从日期设置为文本

excel vba自动过滤范围并删除D列中带有0的所有行

excel:如何将概率与 "or"结合起来?

vba - 第一个 VBA 代码 : Run-time Error "1004"

vba - Excel VBA - 双击时将列中所有选定的单元格大写

Python自定义函数

javascript - 对js函数语法感到困惑