在VBA中创建一个字典,我遇到了一些我很好奇的东西。
当我将Outlook Calendar Item对象添加到字典时,它是ByRef
,但是当我添加一个暗淡的整数时,它是ByVal
。
我的两个问题是:
ByRef
?我看了这个:VB dictionary of Objects 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)是。
tldr;不,你不能为Scripting.Dictionary
ByRef
添加内在类型。 VBA与.NET(与使用分代垃圾收集而不是引用计数)的托管环境类型不同,因此不可能进行适当的内存管理。请注意,.NET Dictionary
对内部类型也不起作用。
对于你的问题的第二部分,要记住的是Dictionary
是一个COM对象 - 当你在一个项目中引用它(或在其中一个类型上调用CreateObject
)时,它会为scrrun.dll启动一个COM服务器向调用者提供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
全权负责管理副本的内存。具有内在类型的VARIANT
包含其数据区域中的内在值 - 而不是指向底层内存的指针。这意味着当marshaller投射它时,唯一最终传递的是值。与此对比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
时,它会将引用计数增加到1。 obj
(局部变量)包含指向该创建对象的指针。当您调用foo.Add "key", obj
时,编组器将对象包装在Variant
中并再次递增引用计数以考虑其指向对象的指针。当程序退出时,obj
会丢失范围,引用计数会减少,计数为1.运行时知道某些东西有一个指向它的指针,因此它不会拆除对象,因为它有可能是将在稍后访问。在foo
被销毁并且对象的最后一次引用减少之前,它不会再次递减引用计数。
至于你的第一个问题,在VBA中做这样的事情的唯一方法是提供你自己的对象包装器来“装箱”这个值:
'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
the default member。然后你的代码看起来更像这样:
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
添加字典方法只是将一个键和项对添加到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中的default,这里仅用于强调intCheck
的整数值将被修改。
并回答你的问题:1。不,整数被添加为值2.它们没有不同的添加,它们都添加相同,这意味着它的值被添加。但是在intCheck
的情况下,它是值本身(例如5)并且仅此而已,但是在gCal
的情况下,该值是数据结构的地址,因此它仍然可以通过该地址到达。