
Картинка: Designed by vectorjuice / Freepik
Кому будет полезна статья, по мнению автора:
начинающим программистам на языке VBA и тем, кто не работал ранее с оператором Type. Если вы используете этот оператор постоянно, можно сравнить свой вариант применения и вариант автора.
Большинство пользователей VBA прекрасно знают такую штуку как Type, он же User Defined Type (UDT). Кто-то, как я, использует его на повседневной основе. Кто-то, возможно, о нем слышал, но не мог понять как его применить.
Лично я помню, как не так давно смотрел на этот Type и пытался понять зачем он мне нужен, ведь он просто хранит в себе переменные, которые можно с тем же успехом объявить в функции/процедуре или на уровне модуля?
В этой статье я хотел бы показать на примере как можно использовать Type. Мы разберем некоторые его особенности, и возможно кто-нибудь из читателей найдет для себя один из примеров крайне интересным (а может быть даже будет использовать в своих проектах). Поехали!
Вычисляем ошибки, чтобы их не допускать
Что же, для начала давайте обратимся к официальной документации:
(вольный перевод автора)
Оператор Type – используется на уровне модуля для объявления пользовательского типа данных, содержащего один или несколько элементов.Type можно использовать только на уровне модуля. После объявления пользовательского типа вы можете объявить переменную этого типа в любом месте в пределах области видимости. Для объявления переменной пользовательского типа используйте Dim, Private, Public, ReDim или Static... Номера и метки строк не допускаются внутри блоков Type...End Type.
Итак, исходя из документации мы можем выделить два основных момента:
Оператор Type используется только на уровне модуля. Это значит, что его нельзя объявлять в процедурах/функциях/методах/свойствах.
Номера и метки строк не допускаются внутри блоков.
Давайте протестируем оба утверждения:
В первом случае получаем ошибку компиляции "Недопустимая внутренняя процедура",

во втором так же ошибка компиляции "Оператор (заявление/утверждение) недопустим внутри блока Type".

Не описано в официальной документации то, что объявленный в Class модуле Type может быть только Private, иначе мы снова получим ошибку компиляции, в этот раз "Нельзя объявлять публичный пользовательский тип в объектном модуле":

Компилятор перестает ругаться только в случае Private Type в Class модуле, но здесь нужно помнить, что возвращать такой UDT можно только Private функцией, иначе:

мы снова получим ошибку компиляции, теперь это "Private перечисления и пользовательские типы, не могут использоваться в качестве параметров или возвращаемых типов для Public процедур, членов данных или полей пользовательских типов".
Кстати, как и обозначено в описании ошибки, в модуле класса нельзя создавать публичные поля или использовать параметры для публичных методов с приватным типом UDT. Ну оно и логично.
Постановка задачи
Итак, если я не ошибаюсь, с ошибками мы разобрались. Перейдем к использованию.
Давайте представим, что наша задача – почтовая рассылка по определенному скрипту. Во время выполнения макроса мы получаем информацию об email-адресе получателя, адресате копии письма и его теме, после чего все эти данные нам нужно передать в отдельную функцию, которая занимается созданием письма и его отправкой или сохранением в черновики.
Решаем без UDT
Для начала разберемся с обычным модулем. Про использование UDT в Class модуле я напишу отдельную статью.
Как можно решить эту задачу стандартными средствами?
Что ж, первое что мы делаем – объявляем переменные, которые будут содержать адрес получателя и адрес адресата копии (простите за тавтологию), а так же тему письма, после чего присваиваем напрямую значения, чтобы не усложнять пример, и отправляем их как аргументы в функцию CreateLetter:

Sub Mailing()
Dim AddressTo As String: AddressTo = "exampleTo@test.vba"
Dim AddressCC As String: AddressCC = "exampleCC@test.vba"
Dim Subject As String: Subject = "Тема письма"
CreateLetter AddressTo, AddressCC, Subject
End SubДалее, пропишем функцию, которая создаст и отправит или сохранит письмо (это значение сделаем необязательным, по умолчанию установим в False):

Sub CreateLetter(ByVal AddressTo As String, _
ByVal AddressCC As String, _
ByVal Subject As String, _
Optional ByVal Submit As Boolean = False)
Dim Outlook As Object
Set Outlook = CreateObject("Outlook.Application")
With Outlook.CreateItem(olMailItem)
.To = AddressTo
.CC = AddressCC
.Subject = Subject
If Submit Then .Send
End With
End SubИтак, в целом все нормально. У нас есть данные, мы передаем их в функцию, функция их использует.
Но это всего лишь два адреса и тема.
А теперь представим, что нам нужно передавать еще текст тела письма и вложение.
А еще в параметрах можно указать нужно ли удалять письмо после отправки (свойство DeleteAfterSubmit), или указать нужно ли отметить неотправленное письмо (черновик) как прочитанное (свойство UnRead).
А еще, возможно нам потребуется создавать письмо из другой процедуры и тогда снова придется перечислять все переменные в объявлении и передавать их все в функцию.
И многое, многое другое...
Представьте на секунду насколько сильно разрастутся параметры функции.
Плюс, копия в письме может быть не всегда, как и вложение. Тогда придется делать все параметры Optional? Или прописать ParamArray? Это все не наглядно и может вызвать ошибки, в случае не верной передачи параметров.
Код становится менее читаемым и сумбурным, согласитесь. На таком небольшом примере все ок, ничего особо критичного. Но в реальном проекте это может стать большой проблемой.
Гораздо более лаконичное решение, как вы уже поняли, использовать UDT.
Решаем с UDT
Для решения нам потребуется объявить Type на уровне модуля и поместить в него все наши переменные. Давайте назовем его TLetter:

Type TLetter
AddressTo As String
AddressCC As String
Subject As String
End TypeДалее, в процедуре Mailing создадим переменную Letter типа TLetter:

Sub Mailing()
Dim Letter As TLetter
Dim AddressTo As String: AddressTo = "exampleTo@test.vba"
Dim AddressCC As String: AddressCC = "exampleCC@test.vba"
Dim Subject As String: Subject = "Тема письма"
CreateLetter AddressTo, AddressCC, Subject
End SubТеперь, всем полям нашего типа присваиваем необходимые значения. Сделать это можно написав имя переменной Letters и далее через точку выбрать нужное поле:

Sub Mailing()
Dim Letter As TLetter
Letter.AddressTo = "exampleTo@test.vba"
Letter.AddressCC = "exampleCC@test.vba"
Letter.Subject = "Тема письма"
CreateLetter Letter
End SubНичего вам это не напоминает?
