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

问题描述 投票:4回答:2

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

当我将Outlook Calendar Item对象添加到字典时,它是ByRef,但是当我添加一个暗淡的整数时,它是ByVal

我的两个问题是:

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

我看了这个: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)是。

vba dictionary outlook outlook-vba
2个回答
5
投票

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);

请注意,KeyItem都指向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

3
投票

添加字典方法只是将一个键和项对添加到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的情况下,该值是数据结构的地址,因此它仍然可以通过该地址到达。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.