Подобно этой проблеме, при использовании объекта Scripting.Dictionary
в VBA результат кода ниже неожиданен.
Option Explicit
Sub test()
Dim d As Variant
Dim i As Integer
Dim s As String
Set d = CreateObject("Scripting.Dictionary")
d.Add "a", "a"
Debug.Print d.Count ' Prints '1' as expected
For i = 1 To d.Count
s = d.Item(i)
Debug.Print s ' Prints ' ' (null) instead of 'a'
Next i
Debug.Print d.Count ' Prints '2' instead of '1'
End Sub
Используя индекс на основе нуля, достигается тот же результат:
For i = 0 To d.Count - 1
s = d.Item(i)
Debug.Print s
Next i
Наблюдая за объектом, я могу видеть, что он имеет два элемента, ключ для недавно добавленного 1
, добавленный из i
. Если я увеличиваю этот цикл до более высокого числа, то количество элементов в словаре увеличивается один раз для каждого цикла.
Я тестировал это в Office/VBA 2003, 2010 и 2013 годах. Все проявляют одинаковое поведение, и я ожидаю, что другие версии (2007) также будут.
Я могу обойти это с помощью других методов looping, но это меня не впечатлило, когда я пытался хранить объекты и получал ожидаемую ошибку объекта в строке s = d.Item(i)
.
Для записи я знаю, что могу делать такие вещи:
For Each v In d.Keys
Set o = d.item(v)
Next v
Но мне больше любопытно, почему я не могу перебирать элементы по номерам.
В соответствии с документацией свойства Item
:
Устанавливает или возвращает элемент для указанного ключа в объекте Dictionary.
В вашем случае у вас нет элемента с ключом 1
:
s = d.Item(i)
фактически создает новую пару ключ/значение в вашем словаре, и значение пустое, потому что вы не использовали необязательный аргумент newItem
.
В словаре также есть метод Items
, который позволяет перебирать индексы:
a = d.Items
For i = 0 To d.Count - 1
s = a(i)
Next i
Добавление в ответ assylias - assylias показывает нам, что D.ITEMS - это метод, который возвращает массив. Зная, что нам не нужен массив вариантов a (i) [см. Ниже описание]. Нам просто нужно использовать правильный синтаксис массива.
For i = 0 To d.Count - 1
s = d.Items()(i)
Debug.Print s
Next i()
KEYS работает одинаково
For i = 0 To d.Count - 1
Debug.Print d.Keys()(i), d.Items()(i)
Next i
Этот синтаксис также полезен для функции SPLIT, которая может помочь сделать это более понятным. SPLIT также возвращает массив с нижними границами в 0. Таким образом, следующие отпечатки "C".
Debug.Print Split("A,B,C,D", ",")(2)
SPLIT - это функция. Его параметры находятся в первом наборе круглых скобок. Методы и функции всегда используют первый набор скобок для параметров, даже если параметры не нужны. В примере SPLIT возвращает массив { "A", "B", "C", "D" }. Поскольку он возвращает массив, мы можем использовать второй набор круглых скобок, чтобы идентифицировать элемент в возвращаемом массиве так же, как и любой массив.
Caveat. Этот более короткий синтаксис может быть не столь эффективным, как использование альтернативного массива a() при итерации по всему словарю, поскольку более короткий синтаксис вызывает метод словарных позиций с каждой итерацией. Более короткий синтаксис лучше всего выщипывать один элемент по номеру из словаря.
Использование d.Keys()(i)
- очень плохая идея, потому что при каждом вызове он будет заново создавать новый массив (у вас будет значительное снижение скорости).
Вот аналог Scripting.Dictionary
под названием "Хэш-таблица" класса от @"The Trick", который поддерживает такой перечислитель: http://www.cyberforum.ru/blogs/354370/blog2905.html
Dim oDict As clsTrickHashTable
Sub aaa()
Set oDict = New clsTrickHashTable
oDict.Add "a", "aaa"
oDict.Add "b", "bbb"
For i = 0 To oDict.Count - 1
Debug.Print oDict.Keys(i) & " - " & oDict.Items(i)
Next
End Sub
Он основан на нуле, поэтому ваш цикл должен быть от 0 до d.Count - 1
.Item(Key)
но также вижу.Key(Key)
, хотя я не уверен, как использовать методKey
... Есть ли способ выполнить итерацию по номеру элемента?