【问题标题】:VBA Array Public Method cant be modified by componentsVBA数组公共方法不能被组件修改
【发布时间】:2020-05-09 22:46:23
【问题描述】:

我正在处理 VBA 中的一个相当复杂的问题,我决定在不同的类中构造它。 其中一个类包含一个数组,我需要在类之外单独修改组件。

问题可以归结为类的定义

Public a As Variant

Private Sub Class_Initialize()
    ReDim a(5) As Double

    ' Set some value
    a(1) = 20
End Sub

还有一个访问数据的子:

Sub Test()

    Dim b As New DummyTest

    b.a(4) = 7

    Debug.Print (b.a(1))
    Debug.Print (b.a(4))

End Sub

调试窗口的结果是20 0。我通过检查检查了这个结果,并确认不能从类的外部修改属性 a。 有趣的是,如果替换为其他数组,则可以修改该属性,例如:

Sub Test2()

    Dim b As New DummyTest

    b.a = Array(1, 2, 3, 4, 5)

    Debug.Print (b.a(1))
    Debug.Print (b.a(4))

End Sub

按预期运行。我很困惑,因为在类上下文和任何其他情况下都允许元素访问,但在这种特定情况下它不会起作用。没有错误消息,它只是拒绝更改数组的内容。

我会找到解决方案的工作,但缺少这种访问数据的方式是一个真正的 PITA。

希望你能帮我解决这个问题。

提前谢谢你!

【问题讨论】:

    标签: arrays vba class


    【解决方案1】:

    您的“属性”正在破坏封装,这几乎违背了它的全部目的。

    Public a As Variant
    

    从公共接口的角度来看,这是一个读写属性(如果您使用Implements DummyTest 添加了一个新的类模块,则必须为其添加Property GetProperty Let 成员)。从类的角度来看,它是一个公共实例字段

    当一个类封装了一个数组、一个集合、一个字典或任何其他数据结构时,您最不希望的就是公开公开该数据结构,任何地方的任何人都可以覆盖该类的整个内部随心所欲地陈述 - 你不会以这种方式获得课程的任何优势。

    正确封装的第一步是创建实例字段Private。下一步是公开实际的Property 访问器。您希望数组数据可写吗?为它公开一个索引的Property Let 成员。

    Private a As Variant
    
    Private Sub Class_Initialize()
        ReDim a(0 To 10)
    End Sub
    
    Public Property Get Value(ByVal index As Long) As Variant
        Value = a(index)
    End Property
    
    Public Property Let Value(ByVal index As Long, ByVal newvalue As Variant)
        a(index) = newvalue
    End Property
    

    此代码的行为与宣传的完全一样:

    Public Sub Test()
    
        Dim b As DummyTest
        Set b = New DummyTest
    
        b.Value(4) = 7
    
        Debug.Print b.Value(1)
        Debug.Print b.Value(4)
    
    End Sub
    

    请注意,由于数组是封装的,客户端代码绝对无法重新分配数组本身:数组被抽象出来,只能通过类公开的方式访问。

    【讨论】:

    • 我不是有意使用封装。我只是想构造数据并保存一些打字。我知道他公开类属性的方式可能是非正统的,但按照你的方式为每个数组编写 6 行附加代码(大约有 20 行,这意味着需要大量输入)。这样做也意味着我失去了将这些属性视为数组的可能性,这在某些情况下我仍然想使用。
    • @sphere 我正在向您展示它是如何工作的,正确。如果您要保存击键,请随意使用全局变量。
    • 听起来很像 X-Y 问题
    • 谢谢@Mathieu,感谢您的意见!可悲的是,我认为 global 不会完成这项工作,因为我需要这个类的多个实例,每个实例都有不同的值。不过还是感谢您的提示!
    • @sphere 我的建议仍然是提出一个可靠且适当封装的数据结构。如果有 20 个数组,而你正在查看 20 个这样的索引属性,那么数据结构是错误的,它不是你需要封装的数组,而是一个字典。或者可能是一个锯齿状数组 - 无论如何这不是 OP 中列出的场景。
    【解决方案2】:

    必须声明VBA中的引用变量,以便我们知道我们引用的是什么类型的对象

    即Dim b As DummyTest ' 这一步不需要新的

    在使用声明的引用来存储信息之前,请创建一个类的实例。

    即设置 b = 新的 DummyTest

    这可能看起来令人困惑,但实际上非常合乎逻辑。因为 b 是一个类,所以 Dim 语句设置 b 来保存 DummyTest 对象的地址。在 Dim 语句的时候,还没有创建对象(没有分配内存),所以 b 的地址是空的(没有值或空)。 set 语句告诉 VBA 创建 DummyTest 类的实例(分配它需要的内存)并将实例的地址(内存)放入变量 b。然后因为 VBA 知道 b 指向一个类的实例,所以当您使用它时,您永远不会看到 b 的地址,而只会看到 b 指向的数据。您可以使用函数 Objptr 获取 b 所持有的地址,但这只是您在非常特殊/特定情况下会做的事情

    【讨论】:

    • As New 声明肯定没有帮助,但是将声明与赋值分开没有区别。
    • @MathieuGuindon 很好。当我将我用 VBA 编写的一些 Advent of code 解决方案转换为 C# 和 Nim 时,我的大脑现在有点发烫。在这个过程中,我正在学习一些我不知道自己做过的假设。
    • 我已经尝试过您提出的解决方案,结果相同。我认为使用: Set b as new Dummy test 已经分配了该部分。但我会检查是否可以使用 Objptr 完成某些操作
    • 在您当前的示例中绝对不需要使用 Objptr。
    猜你喜欢
    • 2019-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-18
    • 2021-01-29
    • 2019-04-14
    • 2014-09-01
    • 1970-01-01
    相关资源
    最近更新 更多