情况是这样的:
我需要从一个函数返回两个值:这个函数需要一个输入参数才能工作。
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 的值?
最佳答案
这里的概念是 ByRef
与 ByVal
参数。
在 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
),然后分配 outName
和 outScope
参数。因为它们是通过引用传递的,所以调用代码可以看到新值——所以调用者看起来像这样:
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
是最安全的参数传递方式)。但是该函数还接收对
scenarioName
和 scenarioScope
变量的引用——当它分配给它的 outName
和 outScope
参数时,该引用所持有的值会相应地更新——调用者可以看到更新的值。用户定义类型
仍然利用
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)、 ScenarioInfo
的 Implements 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/