excel vba - возвращает два значения, передавая один входной параметр - PullRequest
1 голос
/ 25 марта 2019

Это тот случай:
Мне нужно вернуть два значения из функции: для работы этой функции необходим входной параметр.

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 ]

1 голос
/ 25 марта 2019

Вы можете создать массив внутри функции для хранения обоих значений.Затем верните массив.

Например:

'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 голос
/ 25 марта 2019

Концепция, которая здесь используется, - это 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 - в этом случае ...

ООП в полном объеме

Инкапсуляция всего, что вам нужно, в свой собственный модуль класса обеспечивает максимальную гибкость: теперь ваша функция может возвращать ссылку на объект!

Public Function GetScenarioName(ByVal title As String) As ScenrioInfo

Теперь функция может возвращать Nothing, если сценарий не найден, и нет необходимости в каких-либо параметрах, кроме input one:

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 надстройки) с общедоступным фабричным методом , который позволяет параметризировать создание объекта - превращая функцию в нечто вроде этого:

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

Если такой подход вам кажется интересным, вы можете прочитать об этом в блоге Rubberduck News , который я поддерживаю.

0 голосов
/ 25 марта 2019

Используя предложение @ 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...