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

时间:2019-03-25 17:55:46

标签: 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的值?

3 个答案:

答案 0 :(得分:1)

您可以在函数内部创建一个数组来存储两个值。然后,返回数组。

例如:

'If strTitleName is your only argument, then:
Function getScenarioName(strTitleName As String) As Variant

    Dim rng1 As Range
    Dim strSearch As String
    Dim result(1) As String

    activateSheet ("Test Scenarios")

    Set rng1 = Range("B:B").Find(strSearch, , xlValues, xlWhole)
    strSearch = strTitleName & "*"

    result(0) = ""
    result(1) = ""

    If Not rng1 Is Nothing Then

        sName = rng1.Address
        sScope = rng1.Offset(1, 1).Address

        Debug.Print "sName=" & sName
        Debug.Print "sScope=" & sScope

        result(0) = "sName=" & sName
        result(1) = "sScope=" & sScope

    End If

    getScenarioName = result

End Function

答案 1 :(得分:1)

这里使用的概念是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-在这种情况下...

全面吹嘘

将所需的一切封装在自己的类模块中,可以为您带来最大的灵活性:现在,您的函数可以返回对象引用了!

Public Function GetScenarioName(ByVal title As String) As ScenrioInfo

现在,如果未找到任何情况,则该函数可以返回Nothing,并且除了 input 之外,不需要任何其他参数:

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类模块。可能的最简单的实现将只是公开读取/写入公共字段:

Option Explicit
Public Name As String
Public Scope As String

更详尽的实现可能涉及一个IScenarioInfo接口,该接口仅公开Property Get个成员,ScenarioInfo类的Implements IScenarioInfo类,VB_PredeclaredId属性(即。隐藏...,并且使用Rubberduck VBIDE加载项)和公开的 factory方法更容易处理,该方法可以参数化对象的创建-将函数转换为如下所示:

If Not rng1 Is Nothing Then
    Set GetScenarioInfo = ScenarioInfo.Create(rng1.Address, rng1.Offset(1,1).Address)
End If

如果您认为这是一种有趣的方法,则可以在我维护的Rubberduck News博客上进行阅读。

答案 2 :(得分:0)

使用@Freeflow对集合的建议,这是您的更新代码:

Function getScenarioName(strTitleName As String, sName As String, sScope As String) as Collection
    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

        dim colToReturn as Collection
        set colToReturn = New Collection
        colToReturn.Add sName
        colToReturn.Add sScope

        Set getScenarioName = colToReturn

    End If
End Function