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