在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)受此影响。
答案 0 :(得分:5)
tldr; 不,您不能向Scripting.Dictionary
ByRef
添加固有类型。 VBA与.NET的托管环境类型不同(.NET使用世代垃圾收集而不是引用计数),因此无法进行适当的内存管理。请注意,.NET Dictionary
不适用于固有类型。
对于问题的第二部分,要记住的是Dictionary
是一个COM对象-当您在项目中引用它(或以其中一种类型调用CreateObject
时) ,它将启动COM服务器,以供scrrun.dll向调用者提供Scripting
对象。这意味着,当您对其成员之一进行 any 调用时,您将通过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
指针传递给{{1} } 方法。此时,Add
专门负责管理副本的内存。具有内在类型的VARIANT
在其数据区域中包含内在的 value -而不是指向基础内存的指针。这意味着当编组器对其进行转换时,最终唯一通过的是值。将此与Dictionary
进行对比。封装在Object
中的Object
在数据区域中具有指向其Variant
接口的指针,并且封送程序在包装时必须增加引用计数。
将内在类型作为指针传递时的内在问题是,COM事务的任何一方都无法知道谁在超出范围时负责释放内存。考虑以下代码:
IDispatch
问题在于,在执行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
VBA中的对象被引用计数,引用计数确定它们的寿命。仅当引用计数为零时,运行时才会释放它们。在这种情况下,当您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
时,它会将引用计数增加到1。 Set obj = New SomeClass
(局部变量)保存指向该创建对象的指针。当您调用obj
时,编组器将对象包装在foo.Add "key", obj
中,并再次增加引用计数以说明其指向该对象的指针。当过程退出时,Variant
失去作用域,并且引用计数递减,保留计数为1。运行时知道某个对象有指向它的指针,因此它不会删除该对象,因为存在有可能稍后再访问。直到obj
被销毁并且对该对象的最后一个引用减少之后,它才会再次减少引用计数。
关于您的第一个问题,在VBA中执行此类操作的唯一方法是提供您自己的对象包装器以“装箱”值:
foo
如果您想了解它,可以制作'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
the default member。然后您的代码将看起来像这样:
Value
答案 1 :(得分:3)
Dictionary的Add方法仅将键和项对添加到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
的情况下,该值是数据结构的地址,因此仍可以通过该地址来访问