我可以将整数项添加到VBA Dictionary byRef

cas*_*olf 4 outlook vba dictionary outlook-vba

在VBA中创建一个字典,我遇到了一些我很好奇的东西.

当我将Outlook Calendar Item对象添加到字典时ByRef,但当我添加一个灰色的Integer时,它就是ByVal.

我的两个问题是:

  1. 是否可以添加暗淡的整数ByRef
  2. 为什么这两个项目的添加方式不同(我知道一个是对象,一个是基类型,需要更多细节)?

我看了一下:Objects ByRef的VB字典,但它只讨论了对象的情况而不是整数的情况.

以下代码显示了发生的情况:

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,intCheck不受dict中的更改影响,但gCal(1)是.

Com*_*ern 5

tldr; 不,你不能添加内在类型Scripting.Dictionary ByRef.VBA与.NET的管理环境类型不同(使用分代垃圾收集而不是引用计数),因此不可能进行适当的内存管理.请注意,.NET Dictionary也不能以内在类型的方式工作.


对于问题的第二部分,要记住的是DictionaryCOM对象 - 当您在项目中引用它(或调用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);
Run Code Online (Sandbox Code Playgroud)

请注意,KeyItem指针都是Variant.传递Integer(或任何其他内在类型)时,运行时首先执行转换为a Variant,然后将结果Variant指针传递给Add方法.此时,Dictionary它全权负责管理副本的内存.VARIANT具有内在类型的A 包含其数据区域中的内在 - 而不是指向底层内存的指针.这意味着当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
Run Code Online (Sandbox Code Playgroud)

问题是,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
Run Code Online (Sandbox Code Playgroud)

VBA中的对象是引用计数,引用计数决定它们的寿命.运行时只会在引用计数为零时释放它们.在这种情况下,当你Set obj = New SomeClass,它将引用计数增加到1.obj(局部变量)包含指向该创建对象的指针.当您调用时foo.Add "key", obj,编组器将对象包装在a中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
Run Code Online (Sandbox Code Playgroud)

如果你想使用它,你可以成为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
Run Code Online (Sandbox Code Playgroud)

  • 感谢您非常详尽的解释以及基于包装器的解决方法。我承认我必须做一些阅读,因为这个答案最初超出了我的知识范围 - 这正是我所寻找的。 (2认同)