【问题标题】:VBA: Difference in two ways of declaring a new object? (Trying to understand why my solution works)VBA:声明新对象的两种方式的区别? (试图理解为什么我的解决方案有效)
【发布时间】:2011-01-29 12:05:03
【问题描述】:

我正在循环中创建一个新对象,并将该对象添加到集合中;但是当我之后读回这个集合时,它总是完全被我添加的最后一个对象填满。我想出了两种解决方法,但我根本不明白为什么我的初始实现是错误的。

原文:

Dim oItem As Variant
Dim sOutput As String
Dim i As Integer

Dim oCollection As New Collection
For i = 0 To 10
    Dim oMatch As New clsMatch
    oMatch.setLineNumber i
    oCollection.Add oMatch
Next
For Each oItem In oCollection
    sOutput = sOutput & "[" & oItem.lineNumber & "]"
Next
MsgBox sOutput

这导致每个 lineNumber 为 10;我显然不是在创建新对象,而是在每次循环中都使用同一个对象,尽管声明位于循环内部。

所以,我在Next 行之前添加了Set oMatch = Nothing,这解决了问题,它现在是0 到10。所以如果旧对象被显式销毁,那么它愿意创建一个新对象吗?我会认为循环的下一次迭代会导致循环中声明的任何内容都因范围而被破坏?

很好奇,我尝试了另一种声明新对象的方式:Dim oMatch As clsMatch: Set oMatch = New clsMatch。这也导致 0 到 10。

谁能向我解释为什么第一个实现是错误的?

【问题讨论】:

  • 在 VBA 中使用“As New”被认为是不好的做法。当您发现困难的方式时,它可能会导致各种细微的错误。最好在顶部声明所有变量并在适当的地方使用“= New”。 (旁注:我会在 VB.Net 中说完全相反)
  • VBA 中最小的可能范围是过程级; For...Next 块(或任何其他块)不会创建范围。

标签: ms-access syntax vba


【解决方案1】:

当您将 oMatch 对象添加到集合时,它会通过内存引用传递变量。当您再次将 oMatch 声明为新的 clsMatch 时,它不会破坏您创建的第一个对象本地内存指针。它只是为您提供与您创建的第一个 oMatch 对象相同的本地内存位置,即使您已将其声明为新对象。 VBA 使用 ByRef 作为默认的内存传递技术。然后用新更新的行号更新集合内存位置,它们都指向相同的内存位置。因此,所有集合内存指针都将指向您创建的最后一个对象。

当您设置 oMatch = nothing 时,它会重置本地内存指针,并使用新的本地内存指针创建一个新的 oMatch 对象,并且集合的指针将全部指向其正确的对象。

VBA 的默认内存传递是 ByRef,与默认为 ByVal 的 VB 相对,因此您可能会不时遇到这个警告。

【讨论】:

    【解决方案2】:

    Fink 的回答解决了您的主要问题,即您的第一个循环是将多个引用添加到您的集合中的同一个 'clsMatch' 实例。我将详细说明您的修复为何有效。

    在 VBA 中,类似这样的一行:

    Dim c As New Collection
    

    实际上并没有创建一个新的集合。 'Dim' 声明始终只是一个声明。将“As New”形式视为以下内容的简写:

    Dim c As Collection
    '...
    
    '(later, when you're about to use 'c')
    
    If c Is Nothing Then
        Set c = New Collection
    End If
    
    '...
    

    这就是为什么通过将包含它的变量设置为“无”来破坏您的引用是有效的。 [注意:对编辑此内容的人说“不是”-这会改变答案的含义并使其不正确。请阅读原始问题。 OP 发现将变量设置为 Nothing did 有效,我解释了 为什么 是这种情况。] 当循环回到 'oMatch.setLineNumber'行,VBA“有用地”为你的 'oMatch' 变量创建了一个新的 'clsMatch' 实例来引用,然后你的集合中有多个不同的实例。

    明确地这样做可能会更好:

    Dim oMatch As clsMatch   
    
    For i = 0 To 10                
        Set oMatch = New clsMatch                
        oMatch.setLineNumber i                
        oCollection.Add oMatch                
    Next  
    

    请注意(与 C/C++ 或 ??.NET 不同)“Dim”声明的位置无关紧要。它不会在循环内多次“执行”,并且它声明的范围是整个过程,即使它出现在循环内。

    【讨论】:

    • 感谢您解释范围不像 C/C++,这是我更习惯的,就预期行为而言(首先在 C 上学习,所以我假设所有语言都会采取类似行动)。
    • 这就是为什么您经常会看到(正确的,IMO)顾问,例如 Jonathan Allen 对您的问题的评论,即通常最好避免使用“As New”。 'Dim As ' 声明了一些东西。 'Set = New ' 创建东西。但是'Dim As New ' 是一个奇怪的混合体。将“Dim As New”表单看起来像是在创建实例的事实与 VBA 允许您将其置于循环中并且混淆是可以理解的事实相结合。
    【解决方案3】:

    在类模块中有“as new”的有效用法。考虑一下:

    模块一:

    Dim mUbelow as myClassX       ' do not use "as new" here 
    set mUbelow = new myClassX    ' mUbelow instanciation also instanciates subClass 
                                  ' as a referencedClass object
                                  ' so you can not forget to do this
    mUbelow.subClass.someThing = "good news"  ' without the "as new" below: ==> error
    

    class myClassX:

    Public subClass as new referencedClass ' automatic instanciation of subclass:
    

    类引用类:

    Public someThing as string
    

    【讨论】:

      猜你喜欢
      • 2015-09-12
      • 2021-09-28
      • 2011-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多