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