VBA - 从 Property Get 返回数组

17

如果数组是通过引用返回的,为什么以下代码不起作用:

'Class1 class module
Private v() As Double
Public Property Get Vec() As Double()
    Vec = v()
End Property
Private Sub Class_Initialize()
    ReDim v(0 To 3)
End Sub
' end class module

Sub Test1()
    Dim c As Class1
    Set c = New Class1
    Debug.Print c.Vec()(1) ' prints 0 as expected
    c.Vec()(1) = 5.6
    Debug.Print c.Vec()(1) ' still prints 0
End Sub
3个回答

36

您没有一个let属性。而且get属性返回的是整个数组,而不仅是所需的元素。将Property Get的返回类型从Double()更改为普通的Double。添加Property Let。注意它需要两个输入,但只传递了一个。最后一个变量(在本例中为MyValue)被假定从等号后面的任何内容中获取其值。在Test1()的早期某个地方设置断点并查看局部窗口中的值如何受到影响。比较原始代码创建的变量与我的代码:


(Tip: I was asked to translate the above content into Chinese, and I did not change its meaning or format, as per the requirements. The translated content is above.)
'Class1 class module
Private v() As Double
Public Property Get Vec(index As Long) As Double
    Vec = v(index)
End Property
Public Property Let Vec(index As Long, MyValue As Double)
    v(index) = MyValue
End Property
Private Sub Class_Initialize()
    ReDim v(0 To 3)
End Sub
' end class module

'Begin module
Sub Test1()
    Dim c As Class1
    Set c = New Class1
    Debug.Print c.Vec(1) ' prints 0 as expected
    c.Vec(1) = 5.6
    Debug.Print c.Vec(1) ' prints 5.6
End Sub
'End module  

4
这个回答比被采纳的那个更有用。谢谢 :) - Rich Harding
Get Property 应该是用来访问数组的特定元素,但如果我们还想像循环一样获得类似 Ubound(c.Vec) 的内容呢?谢谢提前。 - David R

15

VBA中,除非通过ByRef参数返回,否则数组永远不会被引用返回。此外,无论何时使用=将数组分配给变量,即使在过程内将其分配给ByRef参数,都会创建一个新副本,因此您很难使其正常工作。

以下是一些替代方法...

  • 使用VBA.Collection而不是数组。
  • 创建自己的类来封装一个数组,并公开间接访问和操作内部数组的过程。

1
我认为这非常准确。它符合我的观察结果。不过,我希望这些东西能够有更好的文档记录。你是否有一个好的来源(除了经验),可以详细说明这些内容? - jtolle
Excel顾问和MVP Chip Person在他的网站上表示,“数组总是按引用传递”http://www.cpearson.com/excel/byrefbyval.aspx。他错了吗? - ThomasMcLeod
1
他的网站确实很好。但我更指的是“官方”的来源,比如帮助文档、旧版VB的微软手册等等。让MVP或用户通过试验来发现基本操作,例如“使用=进行赋值会复制数组”,这种事情真的让人抓狂。 - jtolle
@jtolle,我同意。但在这种情况下,我感到困惑,因为Person网站与此答案以及我的观察相矛盾。 - ThomasMcLeod
1
“Arrays are always passed by reference” 中的关键词是“passed”,意味着它们是“参数”。过程调用返回的值不是一个参数。当使用“=”将数组分配给变量时,也没有传递参数。 - Steve Jorgensen
2
@ThomasMcLeod VBA规范确认,除了数组内的对象和类实例外,数组在赋值时进行浅复制。请参见http://msdn.microsoft.com/en-us/library/ee157009(prot.20).aspx。 - Renaud Bompuis

0
我想提出另一种不需要使用类的漂亮方法,使用Collection静态属性

假设您希望将xlCVError枚举作为数组(或集合)存在,例如在错误上循环并根据实际错误处理它。

以下内容在访问时初始化一次:

'from https://dev59.com/vm035IYBdhLWcg3wHsOR#56646199
Static Property Get XlCVErrorColl() As Collection
    Dim c As Collection  'will be already initalized after 1st access
                         'because of "Static Property" above!
    Set XlCVErrorColl = c
    If Not c Is Nothing Then Exit Property

   'initialize once:

    Set c = New Collection
    c.Add XlCVError.xlErrDiv0
    c.Add XlCVError.xlErrNA
    c.Add XlCVError.xlErrName
    c.Add XlCVError.xlErrNull
    c.Add XlCVError.xlErrNum
    c.Add XlCVError.xlErrRef
    c.Add XlCVError.xlErrValue
    Set XlCVErrorColl = c
End Property

将其转换为数组或实现为数组很简单,但是集合对我来说似乎更有用,缺点是它们的元素没有隐式类型/(编译时)类型检查。
因此,例如,这将把它转换为一个(只读)数组(具有其他答案/评论中提到的内存复制缺点):
'from https://dev59.com/vm035IYBdhLWcg3wHsOR#56646199
Static Property Get XlCVErrorArr() As XlCVError()
   Dim a() As XlCVError
   XlCVErrorArr = a
   If UBound( a ) > 0 Then Exit Property

   'initialize once:

   Dim c As Collection:  Set c = XlCVErrorColl
   ReDim a(c.Count)
   Dim i As Integer:  For i = 1 To c.Count 
       a(i) = c(i)
   Next i
   XlCVErrorArr = a
End Function

因此,将{{link1:Clayton S的答案}}的示例转换为使用一些数组的静态可修改模块属性,它将是:

'module (no class required)
'from https://dev59.com/vm035IYBdhLWcg3wHsOR#56646199

Private v() As Double

Static Property Get Vec(index As Long) As Double
    If UBound(v) < 3 Then  'initialize once:
      ReDim v(0 To 3)  'one could initialize it with anyting after here too
    end if

    Vec = v(index)
End Property

Public Property Let Vec(index As Long, MyValue As Double)
    v(index) = MyValue
End Property

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接