Search code examples
arraysexcelvbaclassnested

Universal change of all existing elements in a VBA nested array


I have a composite data structure which I have defined using two nested classes. I am presenting a simplified version of the structure here:-

    Routine  Variable  Type
    -------  --------  ----
    Sub_1    l         Long
             y         Class_A
             z         Class_B     

    Class_A  a()       Class_B  
             i         Integer

    Class_B  s         String

So my my first question: Is the above allowable?

Here is the code:

    '1. sub
    Sub NestedArray()

      Const ciNumberToString As Integer = 64

      Dim l As Long
      Dim y As Class_A
      Dim z As Class_B

      Set y = New Class_A
      Set z = New Class_B
  
      For l = 0 To 10
        y.pI = l
        z.pS = Chr(l + ciNumberToString)
        y.pA = z
      Next l

    End Sub

    '2. Class_A
    Option Explicit

      Private a() As Class_B
      Private i As Integer

      Public Property Get pI() As Integer
        pI = i
      End Property

      Public Property Let pI(oI As Integer)
        If Not oI < i Then
          ReDim Preserve a(oI)
        End If
        i = oI
      End Property

      Public Property Get pA() As Class_B
        Set pA = a(i)
      End Property

      Public Property Let pA(oA As Class_B)
        Set a(i) = oA
      End Property


    '3 Class_B
    Option Explicit

      Private i As Integer

      Public Property Get pI() As Integer
        pI = i
      End Property

      Public Property Let pI(oI As Integer)
        i = oI
      End Property`

I put a stop on the 'End Sub' line. This is the expected result (as it appears in the Watches window):-

    Expression    Value    Type
    ----------    -----    ----
    y                      Class_A
      a                    Class_B(0 to 10)
        a(0)               Class_B
          s       "@"      String
          pS      "@"      String
        a(1)               Class_B
          s       "A"      String
          pS      "A"      String
        a(2)               Class_B
          s       "B"      String
          pS      "B"      String
                  
                  .
                  .
                  .

        a(10)              Class_B
          s       "J"      String
          pS      "J"      String
      i           10       Integer
        pA                 Class_B
          s       "J"      String
          pS      "J"      String
        pI        10       Integer

Here is my actual result:-

    Expression    Value    Type
    ----------    -----    ----
    y                      Class_A
      a                    Class_B(0 to 10)
      a(0)                 Class_B
        s         "J"      String
        pS        "J"      String
      a(1)                 Class_B
        s         "J"      String
        pS        "J"      String
      a(2)                 Class_B
        s         "J"      String
        pS        "J"      String

                  .
                  .
                  .

      a(10)                 Class_B
        s         "J"       String
        pS        "J"       String
      i           10        Integer
    pA                      Class_B
      s           "J"       String
      pS          "J"       String
    pI            10        Integer

y.a(0) is set correctly initially, i.e. it has a value of 0. However on the next iteration it is reset to 1. this is done before y.a(1) is set by the code here:-

    For l = 0 To 10
      y.pI = l
      z.pS = Chr(l + ciNumberToString)
      y.pA = z
    Next l

This is the Class_B code which is executed

    Public Property Let pS(oS As Integer)
      s = oS
    End Property

Because z.pS has changed, y.a is altered from:-

    Expression     Value    Type
    ----------     -----    ----
    y                       Class_A
    a                       Class_B(0 to 10)
      a(0)                  Class_B
        pS         "@"      String
        s          "@"      String

to:-

    y                       Class_A
      a                     Class_B(0 to 10)
        a(0)                Class_B
          pS       "A"      String
          s        "A"      String

... and every iteration of an existing element in the array is set to z.pS


Solution

  • With small modifications, it works:

    Sub NestedArray()
      Const ciNumberToString As Integer = 64
      Dim l As Long
      Dim y As Class_A
      Dim z As Class_B
      Set y = New Class_A
      Set z = New Class_B
      For l = 0 To 10
        Debug.Print "l", l
        y.pI = l
        z.pS = Chr(l + ciNumberToString)
        y.pA = z
        Debug.Print "------------- -------------"
      Next l
    End Sub
    

    Class_A

    Private a() As Class_B
    Private i As Integer
    
    Public Property Get pI() As Integer
      pI = i
    End Property
    
    Public Property Let pI(oI As Integer)
      If i <= oI Then
        ReDim Preserve a(oI)
        i = oI
      End If
    End Property
    
    Public Property Get pA() As Class_B
      Set pA = a(i)
    End Property
    
    Public Property Let pA(oA As Class_B)
      Set a(i) = oA
    End Property
    

    Class_B

    Private i As Integer
    
    Public Property Get pS() As String
    End Property
    
    Public Property Let pS(oA As String)
      i = Asc(oA)
      Debug.Print "pS", oA
    End Property
    

    -->

    l              0 
    pS            @
    ------------- -------------
    l              1 
    pS            A
    ------------- -------------
    l              2 
    pS            B
    ------------- -------------
    l              3 
    pS            C
    ------------- -------------
    l              4 
    pS            D
    ------------- -------------
    l              5 
    pS            E
    ------------- -------------
    l              6 
    pS            F
    ------------- -------------
    l              7 
    pS            G
    ------------- -------------
    l              8 
    pS            H
    ------------- -------------
    l              9 
    pS            I
    ------------- -------------
    l              10 
    pS            J
    ------------- -------------
    

    Surely, 'y.a' contains 11 references to a single Class_B object. And, at the end, any 'a(i).i' is the same - 74 (J). To recover it, modify:

    Sub NestedArray()
      Const ciNumberToString As Integer = 64
      Dim l As Long
      Dim y As Class_A
      Dim z As Class_B
      Set y = New Class_A
      For l = 0 To 10
        Debug.Print "l", l
        y.pI = l
        Set z = New Class_B
        z.pS = Chr(l + ciNumberToString)
        y.pA = z
        Debug.Print "------------- -------------"
      Next l
    End Sub