为什么我不能这样声明我的类对象?

Mir*_*eHF 4 oop excel vba

我目前正在为 VBA 文件创建一个类对象,其目标是充当可以传递单个单元格的范围字典。如果此单元格包含在某个范围内,则它返回与相应范围键关联的值。类名称是“rangeDic”。

它正在制作中,因此其功能尚未实现。这是代码:

Private zone() As String
Private bounds() As String
Private link As Dictionary
Const ContextId = 33

'Init zone
Private Sub Class_Initialize()

    Set link = New Dictionary
    ReDim zone(0)
    ReDim bounds(0)
    
End Sub

'properties
Property Get linkDico() As Dictionary
    Set linkDico = link
End Property

Property Set linkDico(d As Dictionary)
    Set link = d
End Property

Property Get pZone() As String()
    pZone = zone
End Property

Property Let pZone(a() As String)
    Let zone = a
End Property

'methods
Public Sub findBounds()

    Dim elmt As String
    Dim i As Integer
    Dim temp() As String
    
    i = 1
    
    For Each elmt In zone
        ReDim Preserve bounds(i)
        temp = Split(elmt, ":")
        bounds(i - 1) = temp(0)
        bounds(i) = temp(1)
        i = i + 2
    Next elmt

End Sub
Run Code Online (Sandbox Code Playgroud)

我试图在测试子中实例化它,以便调试中间概念。这是代码:

Sub test()

    Dim rd As rangeDic
    Dim ran() As String
    Dim tabs() As Variant
    Dim i As Integer
    
    i = 1
    
    With ThisWorkbook.Worksheets("DataRanges")
        While .Cells(i, 1).Value <> none
            ReDim Preserve ran(i - 1)
            ReDim Preserve tabs(i - 1)
            ran(i - 1) = .Cells(i, 1).Value
            tabs(i - 1) = .Cells(i, 3).Value
            i = i + 1
        Wend
    End With
    
    Set rd = createRangeDic(ran, tabs)

End Sub

Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic

    Dim obje As Object
    Dim zonesL As Integer
    Dim valsL As Integer
    Dim i As Integer
    
    zonesL = UBound(zones) - LBound(zones)
    valsL = UBound(vals) - LBound(vals)
    
    If zonesL <> valsL Then
        Err.Raise vbObjectError + 5, "", "The key and value arrays are not the same length.", "", ContextId
    End If
    
    Set obje = New rangeDic

    obje.pZone = zones()
    
    For i = 0 To 5
        obje.linkDico.add zones(i), vals(i)
    Next i
    
    Set createRangeDic = obje
End Function
Run Code Online (Sandbox Code Playgroud)

看一下 的第 2 行Public Function createRangeDic。我必须将我的对象声明为“Object”,如果我尝试将其声明为“rangeDic”,Excel 将在第 行崩溃obje.pZone = zones()。查看 Windows 事件日志后,我可以看到“错误 1000”类型的应用程序未知错误导致崩溃,其中“VB7.DLL”是有问题的包。

为什么这样 ?难道我做错了什么 ?

感谢您的帮助

编辑:我在 Excel 2016 下工作

Cri*_*use 7

看起来这是一个错误。我的 Excel 没有崩溃,但出现“内部错误”。

\n

因为您有 Java 背景,所以首先让我们澄清一些事情。

\n

数组只能通过引用传递

\n

在 VBA 中,数组只能通过引用另一个方法来传递(除非将其包装在 Variant 中)。所以,这个声明:

\n
Property Let pZone(a() As String) \'Implicit declaration\n
Run Code Online (Sandbox Code Playgroud)\n

等价于:

\n
Property Let pZone(ByRef a() As String) \'Explicit declaration\n
Run Code Online (Sandbox Code Playgroud)\n

当然,还有这个:

\n
Public Function createRangeDic(zones() As String, vals() As Variant) As rangeDic\n
Run Code Online (Sandbox Code Playgroud)\n

等价于:

\n
Public Function createRangeDic(ByRef zones() As String, ByRef vals() As Variant) As rangeDic\n
Run Code Online (Sandbox Code Playgroud)\n

如果您尝试像这样声明方法参数:ByVal a() As String您只会收到编译错误。

\n

数组在赋值时被复制

\n

假设有两个数组称为ab,在执行诸如将数组a = b的副本b分配给之类的操作时a。让我们测试一下。在标准模块中删除以下代码:

\n
Option Explicit\n\nSub ArrCopy()\n    Dim a() As String\n    Dim b() As String\n    \n    ReDim b(0 To 0)\n    b(0) = 1\n    \n    a = b\n    a(0) = 2\n    \n    Debug.Print "a(0) = " & a(0)\n    Debug.Print "b(0) = " & b(0)\nEnd Sub\n
Run Code Online (Sandbox Code Playgroud)\n

运行后ArrCopy我的立即窗口如下所示:
\n在此输入图像描述

\n

如图所示,更改 array 时, array 的内容b不受影响a

\n

ByVal无论您是否指定,属性 Let 始终接收其参数ByRef

\n

让我们测试一下。创建一个名为的类Class1并添加以下代码:

\n
Option Explicit\n\nPublic Property Let SArray(ByRef arr() As String)\n    arr(0) = 1\nEnd Property\n\nPublic Function SArray2(ByRef arr() As String)\n    arr(0) = 2\nEnd Function\n
Run Code Online (Sandbox Code Playgroud)\n

现在创建一个标准模块并添加以下代码:

\n
Option Explicit\n\nSub Test()\n    Dim c As New Class1\n    Dim arr() As String: ReDim arr(0 To 0)\n    \n    arr(0) = 0\n    Debug.Print arr(0) & " - value before passing to Let Property"\n    c.SArray = arr\n    Debug.Print arr(0) & " - value after passing to Let Property"\n    \n    arr(0) = 1\n    Debug.Print arr(0) & " - value before passing to Function"\n    c.SArray2 arr\n    Debug.Print arr(0) & " - value after passing to Function"\nEnd Sub\n
Run Code Online (Sandbox Code Playgroud)\n

运行后Test,我的立即窗口如下所示:
\n在此输入图像描述

\n

因此,这个简单的测试证明了Property Let即使只能传递数组,也会复制数组ByRef

\n

错误

\n

您的原始ran变量 ( )以新名称Sub test传递ByRef,然后再次传递到(属性)。在正常情况下,根据需要多次传递数组应该没有问题,但在这里似乎这是一个问题,因为正在尝试制作副本。createRangeDiczonesByRefpZoneLetByRefProperty Let

\n

有趣的是,如果我们替换它(在里面createRangeDic):

\n
obje.pZone = zones()\n
Run Code Online (Sandbox Code Playgroud)\n

有了这个:

\n
Dim x() As String\nx = zones\nobje.pZone = x\n
Run Code Online (Sandbox Code Playgroud)\n

obje即使声明了,代码运行也没有问题As rangeDic。这是有效的,因为该x数组是该数组的副本zones

\n

看起来无法复制已传递多次Property Let的数组,但如果仅传递一次,它就可以正常工作。也许是因为调用堆栈中添加堆栈帧的方式,存在内存访问问题,但很难说。不管问题是什么,这似乎都是一个错误。ByRefByRef

\n
\n

与问题无关,但我必须补充一些内容:

\n
    \n
  1. 在循环中使用ReDim Preserve是一个坏主意,因为每次为新(更大)数组分配新内存,并且每个元素都会从旧数组复制到新数组。这非常慢。相反,使用Collection注释中建议的 as\n@DanielDu\xc5\xa1ek 或最小化调用次数ReDim Preserve(例如,如果您知道将有多少个值,则只需在开始时对数组进行一次尺寸标注)。
  2. \n
  3. 逐个单元读取速度Range非常慢。Range使用Range.ValueRange.Value2属性(我更喜欢后者)将整个内容读入数组。只要范围超过 1 个单元格,这两种方法都会返回一个数组。
  4. \n
  5. 如果类的私有成员对象负责类的内部工作,则切勿公开该对象。例如,您永远不应该在自定义集合类中公开私有集合,因为它会破坏封装。在您的情况下,linkDico公开了可以从主类实例外部修改的内部字典。也许它不会破坏您的特定示例中的任何内容,但值得一提。另一方面Property Get pZone() As String()是安全的,因为这会返回内部数组的副本。
  6. \n
  7. 添加Option Explicit到所有模块/类的顶部,以确保强制执行正确的变量声明。你的代码无法为我编译,因为none不存在于 VBA 中,除非你在项目的其他地方有它。打开该选项后,我发现了一些其他问题。
  8. \n
\n