Когда я впервые встретил этот класс, подумал «Зачем? Ведь есть простые массивы». А потом попробовал и не представляю как жил без него раньше.

Начну сразу с примера

Предположим, на активном листе в столбце 1 находится список ФИО сотрудников.

Список ФИО
Список ФИО

Наша задача собрать в массив только уникальные ФИО и отсортировать его по убыванию (ну такая вот немного странная задача). Сначала решим ее без использования ArrayList, а в конце сравним результат.

Для получения уникальных значений, создаем функцию GetDistinctItems и в нее передаем столбец с ФИО. В самой функции пробегаем циклом For Each по всем ФИО и добавляем уникальные в объект Buffer (Dictionary). Далее методом Keys извлекаем элементы в дополнительную функцию DescendingSort (используем сортировку пузырьком) и получаем отсортированные значения в переменную Sorted, которую и возвращаем как результат функции.

Public Sub Main()
    Dim FullNameColumn As Range
    Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.

    Dim DistinctList As Variant
    DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
    Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub

Public Function GetDistinctItems(ByRef Range As Range) As Variant
    Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
    Dim Buffer As Object: Set Buffer = CreateObject("Scripting.Dictionary") ' Создаем объект Dictionary.

    Dim Item
    For Each Item In Data
        If Not Buffer.Exists(Item) Then Buffer.Add Item, Empty ' Проверяем наличие элемента и добавляем если отсутствует.
    Next
    
    Dim Sorted As Variant
    Sorted = DescendingSort(Buffer.Keys()) ' Сортируем функцией DescendingSort.
    GetDistinctItems = Sorted ' Возвращаем результат.
End Function

Public Function DescendingSort(ByRef Data As Variant) As Variant
    Dim i As Long
    For i = LBound(Data) To UBound(Data) - 1
        Dim j As Long
        For j = i + 1 To UBound(Data)
            If Data(i) < Data(j) Then
                Dim Temp As Variant
                Temp = Data(j)
                Data(j) = Data(i)
                Data(i) = Temp
            End If
        Next
    Next

    DescendingSort = Data
End Function
Результат
Результат

Тривиально? Вполне. Компактно? Ну в целом да, но в конце мы напишем еще более компактней, а заодно решим проблему написания новой функции, если вдруг результат нужно будет сортировать по возрастанию.

Что есть такое

Начнем с того, что ArrayList это класс из пространства имен System.Collections библиотеки mscorlib, который реализует интерфейс IList. Естественно, в VBA он несколько порезан в плане методов, иначе и быть не могло (например нет методов AddRange или BinarySearch). Но и тем не менее с ним можно (и нужно) работать.
По сути, это динамический массив. Его не нужно самостоятельно переопределять, чтобы изменить размерность, достаточно добавлять элементы с помощью метода Add. Где-то я читал, что на низком уровне (да простят меня знатоки, я не знаю правильно ли я применяю это словосочетание здесь) есть свои нюансы в плане производительности, но, откровенно говоря, за все время использования этого объекта каких-либо проблем я не замечал и время работы макроса из-за него если и растет вообще, то совсем не критично.

В чем же сила брат удобство?

Как минимум в том, что это динамический массив. Вы просто добавляете элементы через метод и не нужно заморачиваться на тему ReDim (и уж тем более Preserve) и вычислений размеров будущего массива.

А дальше начинаются вкусняхи

Во-первых, мы можем выгрузить все элементы одним методом ToArray. Как следует из названия, он преобразует все элементы объекта в обычный массив типа Variant.
Во-вторых, мы можем составлять список уникальных значений, проверяя их наличие методом Contains.

В-третьих, можно забыть про функцию UBound, ведь у этого класса есть свойство Count, которое, как не сложно догадаться, возвращает количество элементов помещенных в объект.
В-четвертых, есть возможность быстро отсортировать элементы как по возрастанию (метод Sort), так и по убыванию (сначала используем метод Sort, а после метод Reverse).

Ну и быстро пробегаем по оставшимся свойствам:

Item(Index)
Предоставляет доступ к элементу по его индексу.

и методам:

IndexOf(Item, StartFrom)
Возвращает индекс элемента. Обязательный аргумент StartFrom поможет найти каждый последующий индекс одинаковых элементов.

RemoveAt(Index)
Удаляет элемент по индексу.

Remove(Item)
Удаляет переданный элемент.

RemoveRange(StartPosition, Count)
Удаляет диапазон элементов. StartPosition указывает на индекс первого элемента, Count на количество элементов в удаляемом диапазоне.

Clear()
Удаляет все элементы.

Insert(Position, Item)
Добавляет элемент по заданной позиции.

Clone()
Создает копию объекта (по сути создает новый объект, а не возвращает ссылку на текущий).

Как создать это чудо

Создать объект класса ArrayList можно с помощью функции CreateObject:

Dim List As Object
Set List = CreateObject("System.Collections.ArrayList")

или через Tools -> Reference подключить библиотеку mscorlib.dll, а дальше создавать как обычный объект:

Dim List As New ArrayList

Минус и той и другой привязки в том, что интерфейс объекта вы не получите. Причину лично я не знаю, но почему-то VBA в Excel (больше нигде не проверял) не видит свойства и методы этого класса (в поздней привязке их и так нет ни у какого объекта, так как тип переменной Object, а вот в ранней обычно есть).

Можно, конечно, получить часть интерфейса, объявив переменную с типом IList и уже после этого присвоить ей инстанс ArrayList, но тем самым мы потеряем бОльшую часть функционала, например методы Sort, ToArray, Reverse.

Вернемся в начало

Помните наш пример? Предлагаю решение с новыми знаниями.

Теперь мы добавляем уникальные значения в объект Buffer (ArrayList), перед этим проверяя методом Contains наличие ФИО в списке элементов. По окончанию цикла применяем метод Sort и Reverse для получения списка по убыванию. Выгружаем результат методом ToArray. Согласитесь на этот раз все гораздо компактней.

Public Sub Main()
    Dim FullNameColumn As Range
    Set FullNameColumn = ActiveSheet.UsedRange.Columns(1) ' Получаем первый столбец.

    Dim DistinctList As Variant
    DistinctList = GetDistinctItems(FullNameColumn) ' Передаем диапазон в функцию.
    Debug.Print Join(DistinctList, vbCrLf) ' Выводим результат.
End Sub

Public Function GetDistinctItems(ByRef Range As Range) As Variant
    Dim Data As Variant: Data = Range.Value ' Преобразуем диапазон в массив.
    Dim Buffer As Object: Set Buffer = CreateObject("System.Collections.ArrayList") ' Создаем объект ArrayList.

    Dim Item
    For Each Item In Data
        If Not Buffer.Contains(Item) Then Buffer.Add Item ' Проверяем наличие элемента и добавляем если отсутствует.
    Next

    Buffer.Sort: Buffer.Reverse ' Сортируем по возрастанию, а потом переворачиваем (по убыванию).
    GetDistinctItems = Buffer.ToArray() ' Выгружаем в виде массива.
End Function
Итоговый результат
Итоговый результат

Что в итоге

В итоге мы имеем преимущество перед классом Collection в том, что есть проверка на наличие элемента в списке (без танцев с бубном) и быстрая выгрузка в виде массива (без написания цикла).

Перед классом Dictionary, пожалуй, преимущество в отсутствии необходимости прописывать ключи (если они изначально не нужны).

Ну и оба вышеперечисленных проигрывают в плане сортировки, добавления элементов по индексу и т.д.

В общем и целом, достаточно удобный в применении класс для работы с одномерными массивами. Конечно, получать данные из объекта Range гораздо проще в обычный массив, но если нужно создавать новый (например в цикле), то, как по мне, ArrayList превосходный вариант.

P.S. (проблемки, проблемушки)

Уже после написания статьи обратил внимание, что мой пример на чистом ПК не работает, появляется automation error -2146232576 при создании объекта ArrayList.

Судя по этому ответу, для работы mscorlib необходимо включить .NET Framework 3.5.

Сделать это можно через Панель управления -> Программы -> Включение или отключение компонентов Windows -> поставить галочку напротив .NET Framework 3.5 (включает .NET 2.0 и 3.0) после чего на ПК скачаются необходимые файлы для работы компонента.

Обязательно после проделанных действий перезагрузить Excel. У меня при установке выдал ошибку. Исправилось выключением Excel и повторным включением компонента.

К слову на моем рабочем ПК таких проблем не было, т.е. данный компонент уже был подключен организацией (или по умолчанию в ранних Windows, не знаю точно).

Спасибо, что прочитали до конца.

Как насчет применения этого класса? Пишите в комментариях!
А также, подписывайтесь на мой 
телеграмм.

Комментарии (21)


  1. LuchS-lynx
    23.01.2023 08:45

    Можно такой же фильтр, т.е. проверку на уникальность, сделать формулами массива самого MS Excel в таблице


    1. starfair
      23.01.2023 09:07
      +1

      Я думаю, что тут скорее вопрос об замене в широком смысле использования. В том же Word нет фильтрации таблицы (по крайней мере в старых версиях офиса). Да и не всегда фильтрация это функция скажем так - известная пользователям, как ни странно это звучит. Иногда такие запросы от пользователей бывают, что даже удивительно почему они не пользуются штатными методами программы, и такое решение именно в макросе - может быть очень даже полезным, в плане экономии сил при разработке. Хотя конечно, все эти подключаемые модули, могут и дурную роль сыграть, когда у кого то без проблем работает, а у кого то надо додумать по началу, почему ошибки валятся, по причине отсутствия таких библиотек


  1. Chupakabra303
    23.01.2023 10:23
    +1

    Вот буквально пару месяцев назад при программировании правда на VBScript в SCADA использовал ArrayList. Вообще, если поковыряться в MS COM интерфейсах, можно найти ещё несколько коллекций или структур данных, типа очереди (Queue), которые можно в VBScript задействовать через COM. Главное, чтобы имелся тривиальный конструктор, в нетривиальные COM не умеет передавать параметры.


  1. xxxphilinxxx
    23.01.2023 11:04
    +2

    Когда сравнивают похожие по назначению структуры данных, ожидаешь хотя бы пару слов о сложности операций (в идеале в O-нотации) и предпочтительности выбора в разных ситуациях, опирающихся не только на удобство стандартной библиотеки: методы можно найти в сторонних библиотеках или написать самому, а вот плюсы и минусы разных подходов весьма постоянны и структуру надо подбирать исходя из назначения.
    С VBA совсем не знаком, но быстрый гуглеж говорит, что ArrayList - это динамический массив, а Collection вовсе построен на (дву-?) связном списке и хеш-таблице одновременно, так что сразу можно предположить, например, что в Collection будет самый быстрый поиск - O(log n), по скорости вставки статический массив всех уделает с O(1) (без учета расширения), ну и так далее. С ростом n разница быстро становится гигантской.

    Где-то я читал, что на низком уровне (да простят меня знатоки, я не знаю правильно ли я применяю это словосочетание здесь) есть свои нюансы в плане производительности, но, откровенно говоря, за все время использования этого объекта каких-либо проблем я не замечал и время работы макроса из-за него если и растет вообще, то совсем не критично.

    Думаю, вам будет интересно таки поглубже изучить классические структуры данных: они не зависят от языка, так что это знание очень универсально. Да и объем материала не особенно велик.
    А вот предсказывать производительность на глаз очень не рекомендую: даже программистам сложно осознать, насколько быстры современные компьютеры, вот хорошая статья была https://habr.com/ru/post/578232/. На глаз не увидеть разницу ни между наносекундой и микросекундой, ни даже между микросекундой и миллисекундой, а это, между прочим, уже 1000x1000. Так бывает, что в деве разработчик запустит метод с условными 100 единицами данных и решит, что он работает практически мгновенно, а на проде туда придет 10 тысяч и все внезапно встанет намертво. О-нотация тем и хороша, что предсказывает, как быстро будет "плохеть" программе при увеличении объема обрабатываемых данных.


    1. ArtCapCorn Автор
      23.01.2023 11:42

      Думаю, вам будет интересно таки поглубже изучить классические структуры данных

      Если речь о hash-таблицах и т.п., то поверхностно ознакомлен. Сильно глубоко не вдавался, т.к. пока не вижу как таковой нужды в плане применения (аналог тех же hash-таблиц в VBA — Dictionary, если я правильно понимаю).

      ожидаешь хотя бы пару слов о сложности операций (в идеале в O-нотации)

      По оценке сложности операций я не силен. В VBA подобная оценка как будто отсутствует (ну по крайней мере у большинства пользователей точно), возможно в этом причина. Поэтому как-то даже в голову не пришло добавить ее в статье. Но свою ошибку я понял, постараюсь на будущее учесть, спасибо!


      1. box0547
        23.01.2023 12:08
        -1

        По оценке сложности операций я не силен. В VBA подобная оценка как будто отсутствует

        В VBA подобная оценка просто лишена смысла!

        Пару лет приходилось поддерживать целое корпоративное приложение на данном фреймворке. Дело было давно, но это извращение я запомнил на всю жизнь... VBA годится исключительно для небольших вспомогательных скриптов, утилит.


        1. ArtCapCorn Автор
          23.01.2023 12:25

          Боюсь даже представить что там было????

          На самом деле, при должном подходе, мне кажется вполне возможно и что-то более серьезное наваять. Но кто бы умел в этот должный подход. (Не претендую на знатока, но пытаюсь как-то туда идти)

          Думаю подавляющее большинство VBA-программистов — люди, которые пару раз юзали макрорекордер и в лучшем случае научились объявлять переменные. Но статистики у меня нет, поэтому это лишь домыслы.


          1. box0547
            24.01.2023 18:56

            Боюсь даже представить что там было????

            Там было это:

            Были и сложные интерактивные формы, и БД на внешнем файле *.xls, конечно же с шифрованием файлов, небольшой бэк-энд, генерация файлов чертежей и другой прилагаемой документации, авто-обновление и, кульминация извращения, поддержка разных версий MS Office. О да, VBA умеет удивлять своими способностями...

            Хотелось бы услышать аргументы минусующих, у них есть положительный опыт разработки и поддержки серьезных приложений на VBA?!

            Часто VBA позиционируется как простой для освоения инструмент, доступный каждому. Но ведь помимо разработки приложение еще нужно поддерживать.

            На странице microsoft достаточно ясно написано о предназначении данного фреймворка, и причины использования (когда VBA уместен): 1. Автоматизация и повторяемость, 2. Расширения возможностей взаимодействия с пользователем, 3. Взаимодействие приложений Office.

            А также есть мягкое предупреждение "Иногда, чтобы достичь поставленных целей, имеет смысл воспользоваться другими методами.".

            Это я еще только про разработку и, само собой, поддержку. Про нестабильность и трудно отлавливаемые ошибки я еще даже не начинал :)


            1. ArtCapCorn Автор
              24.01.2023 19:49

              Было бы интересно узнать мысли автора(ов) этого монстра) ну в смысле: зачем/почему/что мешало и тд

              Бывают просто забавные ситуевины, когда ты чувствуешь потенциал, хочешь его раскрыть, а тебе говорят «нет, батенька, excel и не более, не надо нам ваших C#/Java/C++ etc.»

              Конечно, соглашусь, очень сложно поддерживать большие проекты, как минимум из-за невозможности структурировать файлы. Когда открываешь большой проект и видишь просто кучу модулей, становится немного страшно. Ситуацию немного помогает исправить Rubberduck, но это особо ничего не меняет.

              Плюс, как тут уже заметили, Excel очень любит ни с того ни с сего в любом месте выполнения отлаженного приложения макроса просто упасть и все, без объяснения причины. Поэтому да, скорее это извращение.

              Но черт возьми, мне теперь интересно глянуть на этого монстра????


              1. box0547
                24.01.2023 20:57
                +1

                Ё-моё, этот монстр до сих пор жив!

                Дело было лет 9 назад. Мы ее переписали, а если быть точнее написали заново, прямо почти все с нуля. Разработали нормальное ASP.NET приложение. Пришлось пренебречь интерактивностью и некоторым удобством, но в целом функционал полностью сохранили и даже стало лучше. Меня назначили главным на портирование. Это был целый блокбастер.

                Было бы интересно узнать мысли автора(ов) этого монстра) ну в смысле: зачем/почему/что мешало и тд

                Меня этот вопрос с самого начала терзал, как "посадили" на это чудо. Начало карьеры, боялся высказывать свои возражения руководству. Позднее услышал примерно следующее: "нуу... когда начинали, просто подумали, что офис есть у всех, а чё заморачиваться...". Ближе к своей кончине, программа перестала запускаться, расследование пришло к выводу, что причиной является чрезмерно большой объем кодовой базы, она стала настолько большой, что VBA ну просто может переварить такой объем кода. С трудом продавил портирование на нормальный фреймворк. А после, сменил место работы.

                Срок NDA давно истек. Работал в компании партнере Wilo и дилере разных производителей Siemens, ABB, Schneider Electric, больше не помню. Уже забыл, когда в последний раз лицезрел свое детище, спустя столько лет уже и не надеялся увидеть его снова. Прошерстив сайт wilo, с удивлением обнаружил сей конфигуратор. До сих пор служит верой и правдой!

                Функционал урезан, наверняка с тех пор дорабатывался, изменялся, но это точно наш iSelect (внутренне название проекта), надписи, рисунки, шаги, все свое, родное... Несмотря на кажущуюся простоту, там довольно сложная логика компоновки, совместимостей, зависимостей. Вот он, наша гордость: https://amp.wilo.ru


                1. ArtCapCorn Автор
                  25.01.2023 08:01

                  Прям мини блокбастер???? спасибо

                  нуу... когда начинали, просто подумали, что офис есть у всех, а чё заморачиваться...

                  Интересная концепция????

                  объем кодовой базы, она стала настолько большой, что VBA ну просто может переварить такой объем кода

                  Вот с одной стороны я надеюсь когда нибудь увидеть такой «макрос», с другой, не хотелось бы чтобы этот зверек попал тебе на попечение????


    1. ol_x
      23.01.2023 21:02

      Еще не забываем, что ArrayList оперирует типом object, отсюда объект сразу создается не на стеке, а в куче, потому что используется упаковка (для объекто ладно, но для типов значений - камон Карл), отсюда еще куча всего вытекает... Ну блин, там что нет своих массивов? Ну это точно не правильный способ для VBA


      1. ArtCapCorn Автор
        24.01.2023 06:50

        Позвольте с Вами поспорить.

        Конечно есть свои массивы. А еще есть коллекция. Но можно же и без нее, просто на массивах все делать, верно?

        Ладно, возможно не совсем уместный сарказм, но я вот какую мысль хочу донести.. mscorlib это динамически подключаемая библиотека, верно? Верно. Ровно такая же, как и scrrun (Scripting runtime, в составе которой FileSystemObject и Dicrionary, так горячо любимые всеми VBAшинками). Возможно Вы скажете ими тоже не стоит пользоваться по причинам производительности? Тогда точно останутся одними массивы да коллекция, с которой не особо удобно (ну лично мне, возможно кто-то со мной и не согласится) работать, т.к. ключ/значение в нее добавлять можно, а проверить наличие ключа (встроенными средствами) нет, приходится изворачиваться. Метод извлечения списка ключей/значений? Не, не слышал. Про FSO вообще молчу, стандартные функции для работы с файлами крайне скудны. Я пробовал принципиально ими пользоваться, тяжко.

        Если не использовать тип Object, а пользоваться ранней привязкой, то конечный клиент может словить интересные ошибки (ну не поддерживают VBA, куча багов в нем, которые никто уже не исправит). Поэтому только поздняя привязка, поэтому только через тип Object, хоть мне поначалу это дико не нравилось и я пытался продвигать раннюю привязку, но после нескольких обращений от клиентов о том, что у них что-то не работает из-за этого (даже, казалось бы, такие родные либы Word и Outlook), пришлось смириться.

        Это я все к чему? Все эти библиотеки и прочее - они ведь сделаны для удобства программиста, чтобы ему не нужно было сотни раз переписывать один и тот же код и можно было пользоваться уже готовыми и проверенными решениями. Да, порой они дают маленькую просадку в плане производительности, но, наверное, иногда можно пожертвовать этими нано-миллисекундами в угоду удобства, читаемости кода (возможно я и не прав, но это сугубо личное мнение). В VBA и без ArrayList кучу всего приходится писать самостоятельно, что в других языках решается подключением какой-либо библиотеки.


        1. ol_x
          24.01.2023 22:39

          Ну в писанине каждый делает, что хочет... только вот потом приходят не совсем понимающие, видят "правильно" и начинают думать, что это действительно правильно. Называйте вещи своими именами - просто удобно.


          1. ArtCapCorn Автор
            25.01.2023 08:03

            Вы про заголовок? Надо было добавить дисклеймер, что это, конечно, для красного словца) кликбейт, так сказать

            Прошу прощения, если ввел в заблуждение

            Добавлю в ближайшее время????


  1. khe404
    23.01.2023 14:48
    +1

    Люблю Excel и как результат порой использую VBA.

    Можно декларировать в VBA внешние библиотеки, передавать параметры, получать результат. (Как мне помнится можно самому наклепать dll и использовать функции написанные на чистом С, достаточно описать интерфейсы методов). А можно описать свой собственный объект который будет работать с выделением памяти и копированием даннных.

    Проблемы возникают следующего характера:

    1. стабильность работы. ( excel вдруг ни с того ни с сего падает или зависает при использовании внешних объектов) Может конечно в последние годы что-то поправили, но исторически, любой внешний модуль это неожиданные падения с откатом к уже успевшей устареть версии файлика.

    2. Во встроенном редакторе нет возможности использовать автодополнение (autocompletion) в редактировании кода использующего Variant object. ( не сказать чтобы критично, но неудобно )

    3. Сильно сокращаются возможности отладки.

    Автор сравнивает написание собственного куска кода сортировки и поиска уникальных записей с уже кем-то написанным. Да, удобно, использовать уже разработанные методы, но еще лучше использовать инструкции excel. Оные отрабатывают на порядок быстрее, не требуют подключения внешних модулей, поддерживаются разработчиком самого excel и достаточно стабильны.

    Посему вместо закачивания данных в массив задачу предложенную автором лучше решать через копирование листа, сортировку таблицы, удаление дубликатов. ( задрафтить подобный VBA код можно даже с использованием записи макро, а после отшлифовать дополнив отключением отображения изменений на время работы кода и указав относительные ссылки)

    Получиться что то вроде:

    Sub Sort_RemDuplicates()
        ActiveSheet.UsedRange.Columns(1).RemoveDuplicates Columns:=1, Header:=xlYes
        With ActiveSheet.Sort
            .SortFields.Clear
            .SortFields.Add2 Key:=Columns(1) _
            , SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
            .SetRange Columns(1)
            .Header = xlYes
            .MatchCase = False
            .Orientation = xlTopToBottom
            .SortMethod = xlPinYin
            .Apply
        End With
    End Sub
    

    Будет на порядок быстрее и надежнее чем работать с массивами самостоятельно.

    При условии отключения автоподстча, обновления связей и перерисовки.


    1. ArtCapCorn Автор
      23.01.2023 15:19

      Спасибо за развернутый комментарий)

      Задача, приведенная в статье, просто пример который пришел в голову. Возможно решение не самое действенное, но наглядно показывающее как можно упростить задачу. А ведь бывают задачи, когда нужно работать именно с массивом (да что там бывают, вечно есть).

      Согласен, встроенные возможности Excel чаще гораздо удобнее, чем сочиненные самостоятельно, но их все таки часто не хватает.


      1. khe404
        23.01.2023 18:42

        Ну, тут хозяин барин.

        Если таких задач много, это признак того, что в вашей специфической области excel возможно не самый подходящий инструмент.


        1. ArtCapCorn Автор
          23.01.2023 18:54

          Увы в моей работе принцип:

          Интрументы как и родителей не выбирают????

          Ну то есть, необходимо именно с Excel работать. Возможно в будущем, когда-нибудь. Но пока так.


  1. velipre_xella
    23.01.2023 18:55

    Эта фича к версии MS Excel привязана или к .NET?

    В MS Excel 2007 будет работать?


    1. ArtCapCorn Автор
      23.01.2023 18:56

      Насколько я понял, к .NET.

      Не уверен, но думаю от версии Excel она не зависит.