Могу ли я добавить целочисленные элементы в словарь VBA byRef - PullRequest
0 голосов
/ 09 февраля 2019

Создание словаря в VBA, и я наткнулся на то, что мне показалось странным.

Когда я добавляю объект элемента календаря Outlook в словарь, это ByRef, но когда я добавляю затемненное целое число, этоByVal.

Мои два вопроса:

  1. Можно ли добавить затемненное целое число ByRef?
  2. Почему эти два элемента добавляются по-разному (Я знаю, что один является объектом, а другой является базовым типом, ища немного больше деталей)?

Я смотрел на это: VB словарь объектов ByRef , но это толькоговорит о случае объекта, а не о целом случае.

Вот код, показывающий, что происходит:

Sub checkbyref()

    Dim gCal As Items
    Dim dict As New Scripting.Dictionary
    Set dict = New Scripting.Dictionary
    Dim intCheck As Integer

    intCheck = 5
    Set gCal = GetFolderPath("\\GoogleSync\GoogleSyncCal").Items 'gets caledar items based on path

    strMeetingStart = "01/5/2019 12:00 AM"
    strGSearch = "[Start] >= '" & strMeetingStart & "'"

    gCal.Sort "[Start]"
    Set gCal = gCal.Restrict(strGSearch)

    Debug.Print intCheck 'prints "5"
    Debug.Print gCal(1).Start 'prints 1/7/2019 9:30:00 AM"

    dict.Add "check", intCheck
    dict.Add "cal", gCal(1)

    'direction 1
    dict("check") = 4
    dict("cal").Start = "1/1/2020 9:00 AM"
    Debug.Print intCheck 'prints "5"
    Debug.Print gCal(1).Start 'prints "1/1/2020 9:00:00 AM"


    'direction 2
    intCheck = 6
    gCal(1).Start = "1/1/2021 9:00 AM"
    Debug.Print dict("check") 'prints "4"
    Debug.Print dict("cal").Start 'prints "1/1/2021 9:00:00 AM"

End Sub

Как вы можете видеть, intCheck не зависит от изменений в dict, но gCal (1) есть.

Ответы [ 2 ]

0 голосов
/ 09 февраля 2019

Метод добавления словаря просто добавляет пару ключа и элемента в объект Dictionary.В случае dict.Add "check", intCheck элемент здесь является целочисленным значением.это означает, что нет добавленной ссылки, только целочисленное значение.Если вы хотите отследить исходную переменную обратно, вам нужно будет обернуть ее в класс, как предложил пользователь Comintern, или вам придется одновременно обновлять словарь и исходную целочисленную переменную.Пример:

Sub checkbyref()
    Dim dict As New Scripting.Dictionary
    Set dict = New Scripting.Dictionary
    Dim intCheck As Integer

    intCheck = 5
    Debug.Print intCheck 'prints "5"
    dict.Add "check", intCheck

    ' dict("check") = 4
    SetDictionaryWithInteger dict, "check", 4, intCheck
    Debug.Print dict("check") & "|" & intCheck

    ' intCheck = 6
    SetDictionaryWithInteger dict, "check", 6, intCheck
    Debug.Print dict("check") & "|" & intCheck

End Sub

Private Sub SetDictionaryWithInteger( _
    dic As Scripting.Dictionary, _
    key As String, _
    newVal As Integer, _
    ByRef source As Integer)
    dic(key) = newVal
    source = newVal
End Sub

Примечание: ByRef - это значение по умолчанию в Visual Basic, здесь оно используется только для того, чтобы подчеркнуть, что целочисленное значение intCheck будет изменено.

И чтобы ответить на ваши вопросы: 1. нет, целое число добавляется как значение 2. они не добавляются по-разному, они оба добавляются одинаково, что означает, что его значения добавляются.Но в случае intCheck это само значение (например, 5) и ничего более, но в случае gCal значение является адресом структуры данных, поэтому до него все еще можно добраться по этому адресу.

0 голосов
/ 09 февраля 2019

tldr; Нет, вы не можете добавить внутренний тип к Scripting.Dictionary ByRef.VBA не относится к управляемой среде того же типа, что и .NET (которая использует сборку мусора поколений вместо подсчета ссылок), поэтому правильное управление памятью было бы невозможно.Обратите внимание, что .NET Dictionary также не работает таким образом с внутренними типами.


Что касается второй части вашего вопроса, следует иметь в виду, что Dictionary являетсяCOM-объект - когда вы ссылаетесь на него в проекте (или вызываете CreateObject для одного из его типов), он запускает COM-сервер для scrrun.dll, чтобы предоставить Scripting объекты вызывающей стороне.Это означает, что когда вы делаете любой вызов одного из его участников, вы передаете все аргументы через маршаллер COM.Метод Add является членом интерфейса IDictionary и имеет такое описание интерфейса:

[id(0x00000001), helpstring("Add a new key and item to the dictionary."), helpcontext(0x00214b3c)]
HRESULT Add(
                [in] VARIANT* Key, 
                [in] VARIANT* Item);

Обратите внимание, что и Key, и Item являются указателями на Variant.Когда вы передаете Integer (или любой другой внутренний тип), среда выполнения сначала выполняет приведение к Variant, а затем передает результирующий указатель Variant методу Add.На этом этапе Dictionary несет полную ответственность за управление памятью копии.A VARIANT с внутренним типом содержит значение внутреннего в своей области данных, а не указатель на базовую память.Это означает, что когда маршаллер разыгрывает его, единственной вещью, которая в конечном итоге проходит, является значение.Сравните это с Object.Object, обернутый в Variant, имеет указатель на свой интерфейс IDispatch в области данных, и маршаллеру приходится увеличивать счетчик ссылок при его переносе.


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

Dim foo As Scripting.Dictionary  'Module level

Public Sub Bar()
    Dim intrinsic As Integer

    Set foo = New Scripting.Dictionary
    foo.Add "key", intrinsic
End Sub

Проблема в том, что intrinsic выделяется память в стеке при выполнении Bar, но foo не освобождается при выходе из процедуры - intrinsic,Если во время выполнения передается intrinsic в качестве ссылки, у вас останется неверный указатель, сохраненный в foo после освобождения этой памяти.Если вы попытаетесь использовать его позже в другой процедуре, вы получите либо значение корзины, если память была использована повторно, либо нарушение прав доступа.


Теперь сравните это с передачей Object:

Dim foo As Scripting.Dictionary  'Module level

Public Sub Bar()
    Dim obj As SomeClass

    Set foo = New Scripting.Dictionary
    Set obj = New SomeClass
    foo.Add "key", obj
End Sub

Объекты в VBA подсчитываются, и счетчик ссылок определяет срок их службы.Среда выполнения освободит их только тогда, когда счетчик ссылок равен нулю.В этом случае, когда вы Set obj = New SomeClass, счетчик ссылок увеличивается до единицы.obj (локальная переменная) содержит указатель на этот созданный объект.Когда вы вызываете foo.Add "key", obj, маршаллер оборачивает объект в Variant и снова увеличивает счетчик ссылок, чтобы учесть его указатель на объект.Когда процедура завершается, obj теряет область видимости, а счетчик ссылок уменьшается на 1, оставляя счетчик 1. Время выполнения знает, что на что-то есть указатель, поэтому оно не разрушает объект, потому что есть вероятностьчто это будет доступно позже.Он не будет уменьшать счетчик ссылок снова, пока foo не будет уничтожен, а последняя ссылка на объект не уменьшена.


Что касается вашего первого вопроса, единственный способ сделать что-то подобное в VBAбыло бы предоставить вашу собственную обертку для объекта "box" в значении:

'BoxedInteger.cls
Option Explicit

Private boxed As Integer

Public Property Get Value() As Integer
    Value = boxed
End Property

Public Property Let Value(rhs As Integer)
    boxed = rhs
End Property

Если вы хотите поэкспериментировать с этим, вы можете сделать Value элементом по умолчанию .Тогда ваш код будет выглядеть примерно так:

Dim dict As New Scripting.Dictionary
Set dict = New Scripting.Dictionary
Dim check As BoxedInteger

Set check = New BoxedInteger
check.Value = 5

dict.Add "check", check
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...