【问题标题】:Array of Class Module type not updating类模块类型的数组未更新
【发布时间】:2017-06-13 17:23:55
【问题描述】:

我碰到了一些奇怪的数组,我可以为整个数组赋值,但不能为单个数组成员赋值。

这是一个数据类模块,名为Class_E

    Public Name As Variant
    Public Age As Variant

这是正文代码,它只是为 2 个数组分配和重新分配值

Sub main()
    Dim r As Class_E
    Set r = Band()
End Sub

Function Band() As Class_E
    Set Band = New Class_E

    'Part 1: Initialize the variables
    Debug.Print "Part 1"
    Band.Name = Array("Edison", "Tesla", "Faraday", "Turing")
    Debug.Print "Before: Name={" & Band.Name(0) & ", " & Band.Name(1) & ", " & Band.Name(2) & ", " & Band.Name(3) & "}"
    Band.Age = Array(10, 10, 10, 10)
    Debug.Print "Before: Age={" & Band.Age(0) & ", " & Band.Age(1) & ", " & Band.Age(2) & ", " & Band.Age(3) & "}"

    'Part 2: Change values of arrays, by item
    Debug.Print "Part 2"
    For k = LBound(Band.Name) To UBound(Band.Name)
        Debug.Print "k=" & k
        Band.Name(k) = "Foo"
        Band.Age(k) = 999
    Next k
    Debug.Print "After: Name={" & Band.Name(0) & ", " & Band.Name(1) & ", " & Band.Name(2) & ", " & Band.Name(3) & "}"
    Debug.Print "After: Age={" & Band.Age(0) & " " & Band.Age(1) & " " & Band.Age(2) & " " & Band.Age(3) & "}"

    'Part 3: Change values of array, entirely
    Debug.Print "Part 3"
    Band.Name = Array("Spring", "Summer", "Autumn", "Winter")
    Debug.Print "Again: Name={" & Band.Name(0) & ", " & Band.Name(1) & ", " & Band.Name(2) & ", " & Band.Name(3) & "}"
    Band.Age = Array(11, 11, 11, 11)
    Debug.Print "Again: Age={" & Band.Age(0) & ", " & Band.Age(1) & ", " & Band.Age(2) & ", " & Band.Age(3) & "}"

    'Part 4: Use another temp array variable
    Debug.Print "Part 4"
    Dim Temp_Name As Variant, Temp_Age As Variant
    Temp_Name = Band.Name
    Temp_Age = Band.Age
    For k = LBound(Band.Name) To UBound(Band.Name)
        Debug.Print "k=" & k
        Temp_Name(k) = "Foo"
        Temp_Age(k) = 999
    Next k
    Debug.Print "Temp: Name={" & Temp_Name(0) & ", " & Temp_Name(1) & ", " & Temp_Name(2) & ", " & Temp_Name(3) & "}"
    Debug.Print "Temp: Age={" & Temp_Age(0) & " " & Temp_Age(1) & " " & Temp_Age(2) & " " & Temp_Age(3) & "}"

    'Part 5: Original arrays again
    Debug.Print "Part 5"
    Debug.Print "Again: Name={" & Band.Name(0) & ", " & Band.Name(1) & ", " & Band.Name(2) & ", " & Band.Name(3) & "}"
    Debug.Print "Again: Age={" & Band.Age(0) & ", " & Band.Age(1) & ", " & Band.Age(2) & ", " & Band.Age(3) & "}"

End Function

这是我得到的输出屏幕

Part 1
Before: Name={Edison, Tesla, Faraday, Turing}
Before: Age={10, 10, 10, 10}
Part 2
k=0
k=1
k=2
k=3
After: Name={Edison, Tesla, Faraday, Turing}
After: Age={10 10 10 10}
Part 3
Again: Name={Spring, Summer, Autumn, Winter}
Again: Age={11, 11, 11, 11}
Part 4
k=0
k=1
k=2
k=3
Temp: Name={Foo, Foo, Foo, Foo}
Temp: Age={999 999 999 999}
Part 5
Again: Name={Spring, Summer, Autumn, Winter}
Again: Age={11, 11, 11, 11}

问题:

1. 为什么第 2 部分中的简单作业没有执行?并且没有错误消息。如第 3 部分所示,重新分配数组没有问题。

2. 为什么第 4 部分中的“Temp_Name = Band.Name”是数组值的副本而不是数组指针?没有为临时数组分配内存的指令。

非常感谢您的意见。

【问题讨论】:

  • 您应该使用Band(0 to 3),而不是Band.Name(0 to 3)。循环遍历将是Band(k).Name = "Foo"
  • 使用数组作为类成员是有问题的。请改用集合或字典。您还可以设想@Jeeped 建议的更好的设计方案。此外,Name 是 VBA 中的关键字,请勿将其用于变量。
  • @Jeeped,事实上,这是一个较大项目的孤立问题。我需要一个数组作为类结构的一部分,所以不能做 Band(0 到 3)。
  • @A.S.H.我们可以将名称更改为其他名称,但问题仍然存在
  • 对于数组,使用类型 (udt)。因此,您不会被使用令人困惑的变体所困。对于类类型,我推荐字典,比集合更灵活。

标签: arrays vba excel vb6


【解决方案1】:

使用数组作为公共类成员是有问题的。 VBA 不允许这样做是有原因的。您试图通过将成员声明为 Variant 来克服此限制,但这并不能解决问题。

您可以使用Collections 或Dictionarys,因为这些是Objects,所以您可以在它们上使用Set引用。但是 VBA 数组是 值类型。因此,当您从函数中获取数组时,您将获得该数组的副本

  1. 为什么不执行第 2 部分中的简单分配?并且没有错误消息。我们可以按照第 3 部分所示重新分配数组。

因为您实际上是在处理数组的副本。在声明中

Band.Name(k) = "Foo"

Band.Name虽然看起来是直接访问类的一个字段,但实际上是通过一个属性get "wrapper"来访问它;公共类成员总是如此,编译器会自动添加 COM 的 IDispatch 接口和最终后期绑定所需的 get/let 包装器(访问object 引用中的类)。包装器返回数组的副本不是引用,因为如前所述,VBA 中的数组(以及字符串)是值类型,而不是对象。所以你在数组的副本上工作。

您可以通过添加在成员数组中分配条目的Property Let 方法来解决此问题。为您的班级考虑以下代码:

Public Names As Variant '<-- Change to plural to add property get/let
Public Ages As Variant  ' <-- Same

Public Property Let Name(i As Long, s As String)
  Names(i) = s
End Property

Public Property Let Age(i As Long, val As Long)
  Ages(i) = val
End Property

您可以使用这些来分配成员数组中的条目。然后您的第 2 部分将按预期工作。

  1. 为什么第 4 部分中的“Temp_Name = Band.Name”是数组值的副本而不是数组指针?没有为临时数组分配内存的指令。

如前所述,数组是值类型,而不是对象。将数组分配给变量总是会创建一个副本,而不是像对象案例那样的重复引用。 (加上包装器属性 get ,您会发现自己距离初始类的数组两英里远)。

【讨论】:

  • @Jeeped 谢谢。您建议的设计更改也很好,但是当 OP 坚持认为该类出于某种原因必须具有那些“数组成员”时,我写了这个......
  • 即使在此代码中 namesages 被声明为私有并且具有除变体以外的类型(例如 private names() as Long),它是否仍可用作公共数组类,并保持属性不变?
  • @PatrickLepelletier 最好将成员设为私有并提供访问属性。实际上(正如我在答案中提到的)公共成员只不过是具有编译器生成的访问属性的私有成员。问题的症结在于,在任何情况下,您都不会得到成员数组的引用,而是一个副本(除非使用一些指针黑客/技巧)。
【解决方案2】:

根据我的建议,将数组从 Band.Name(0 到 3) 存储到 Band(0 到 3).Name,这里是一些工作代码。

Class_E(代码)类模块代码表

Option Explicit

Private pName As String
Private pAge As Long

Public Property Get Name() As String
    Name = pName
End Property
Public Property Let Name(Value As String)
    pName = Value
End Property

Public Property Get Age() As Long
    Age = pAge
End Property
Public Property Let Age(Value As Long)
    pAge = Value
End Property

标准模块 1(代码)代码表

Option Explicit

Sub main()
    Dim i As Long, r() As New Class_E
    r = fillBand()
    For i = LBound(r) To UBound(r)
        Debug.Print r(i).Name & " - " & r(i).Age
    Next i
End Sub

Function fillBand() As Class_E()
    Dim tmpBands() As New Class_E, tmp1 As Variant, tmp2 As Variant, i As Long

    'Part 1: Initialize the variables
    Debug.Print "Part 1"
    tmp1 = Array("Edison", "Tesla", "Faraday", "Turing")
    tmp2 = Array(5, 10, 15, 20)
    ReDim tmpBands(LBound(tmp1) To UBound(tmp1))

    For i = LBound(tmp1) To UBound(tmp1)
        With tmpBands(i)
            .Name = tmp1(i)
            .Age = tmp2(i)
        End With
    Next i

    Debug.Print "Before: Name={" & tmpBands(0).Name & ", " & tmpBands(1).Name & ", " & tmpBands(2).Name & ", " & tmpBands(3).Name & "}"
    Debug.Print "Before: Age={" & tmpBands(0).Age & ", " & tmpBands(1).Age & ", " & tmpBands(2).Age & ", " & tmpBands(3).Age & "}"

    'Part 2: Initialize the variables
    Debug.Print "Part 2"
    tmp1 = Array("foo", "bar", "foobar", "else")
    tmp2 = Array(9, 99, 999, 9999)
    ReDim tmpBands(LBound(tmp1) To UBound(tmp1))

    For i = LBound(tmp1) To UBound(tmp1)
        tmpBands(i).Name = tmp1(i)
        tmpBands(i).Age = tmp2(i)
    Next i

    Debug.Print "Before: Name={" & tmpBands(0).Name & ", " & tmpBands(1).Name & ", " & tmpBands(2).Name & ", " & tmpBands(3).Name & "}"
    Debug.Print "Before: Age={" & tmpBands(0).Age & ", " & tmpBands(1).Age & ", " & tmpBands(2).Age & ", " & tmpBands(3).Age & "}"

    'Part 3: Initialize the variables
    Debug.Print "Part 3"
    tmp1 = Array("spring", "summer", "fall", "winter")
    tmp2 = Array(1, 11, 111, 1111)
    ReDim tmpBands(LBound(tmp1) To UBound(tmp1))

    For i = LBound(tmp1) To UBound(tmp1)
        tmpBands(i).Name = tmp1(i)
        tmpBands(i).Age = tmp2(i)
    Next i

    Debug.Print "Before: Name={" & tmpBands(0).Name & ", " & tmpBands(1).Name & ", " & tmpBands(2).Name & ", " & tmpBands(3).Name & "}"
    Debug.Print "Before: Age={" & tmpBands(0).Age & ", " & tmpBands(1).Age & ", " & tmpBands(2).Age & ", " & tmpBands(3).Age & "}"

    fillBand = tmpBands
End Function

来自即时窗口的结果

Part 1
Before: Name={Edison, Tesla, Faraday, Turing}
Before: Age={5, 10, 15, 20}
Part 2
Before: Name={foo, bar, foobar, else}
Before: Age={9, 99, 999, 9999}
Part 3
Before: Name={spring, summer, fall, winter}
Before: Age={1, 11, 111, 1111}
Returned Values:
spring - 1
summer - 11
fall - 111
winter - 1111

【讨论】:

  • 不错的修改。不幸的是,我有不同大小的数组,所以必须将它们保留为对象中的数组,而不是转换为对象数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-12
  • 2018-07-01
  • 2017-01-25
  • 2011-12-05
  • 1970-01-01
  • 2011-12-01
相关资源
最近更新 更多