Collections of classes in VBA

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
1
down vote

favorite












I have noticed this question https://stackoverflow.com/questions/49389392/nested-classes-and-collections-vba in StackOverflow, but it got closed faster than I could have answered. However, it was concerning a best practice for usage of collections of classes.



I usually use Array and I add to it like this:



Public Sub AddToTeam(emp As Employee)

Dim cnt As Long
cnt = UBound(pTeam)

If Not pTeam(0) Is Nothing Then
ReDim Preserve pTeam(cnt + 1)
cnt = cnt + 1
End If
Set pTeam(cnt) = emp

End Sub


Furthermore, I initialize the Array like this in the class:



Private Sub Class_Initialize()
ReDim pTeam(0)
End Sub



This is the working code, feel free to give any ideas:



Main Module



Option Explicit

Public Sub TestMe()

Dim EmpA As New Employee
Dim EmpB As New Employee
Dim ManA As New Employee
Dim TeamA As New Team

ManA.Name = "John Doe Top Manager"
EmpA.Name = "Peter"
EmpB.Name = "George"

Set EmpB.Manager = ManA
TeamA.Name = "The best team!"

TeamA.AddToTeam ManA
TeamA.AddToTeam EmpA
TeamA.AddToTeam EmpB

TeamA.PrintNames
Debug.Print TeamA.Name
TeamA.PrintInfoForManagers

End Sub


Class Employee



Option Explicit

Private pName As String
Private pManager As Employee
Private pAge As Long
Private pTeam As String
Private pHasManager As Boolean

Public Property Get HasManager() As Boolean
HasManager = pHasManager
End Property

Public Property Let HasManager(Value As Boolean)
pHasManager = Value
End Property

Public Property Get Manager() As Employee
Set Manager = pManager
End Property

Public Property Set Manager(Value As Employee)
Set pManager = Value
HasManager = True
End Property

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

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

Public Property Get Team() As Employee
Team = pTeam
End Property

Public Property Let Team(Value As Employee)
pTeam = Value
End Property


Class Team



Option Explicit

Private pTeam() As Employee
Private pName As String

Public Sub PrintInfoForManagers()

Dim emp As Variant
For Each emp In pTeam
If emp.HasManager Then
Debug.Print emp.Name & " is managed by " & emp.Manager.Name
Else
Debug.Print emp.Name & " has no manager."
End If
Next emp

End Sub

Public Sub PrintNames()

Dim emp As Variant
For Each emp In pTeam
Debug.Print emp.Name
Next emp

End Sub

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

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

Public Sub AddToTeam(emp As Employee)

Dim cnt As Long
cnt = UBound(pTeam)

If Not pTeam(0) Is Nothing Then
ReDim Preserve pTeam(cnt + 1)
cnt = cnt + 1
End If
Set pTeam(cnt) = emp

End Sub

Private Sub Class_Initialize()
ReDim pTeam(0)
End Sub



So how would you review/improve it?







share|improve this question



























    up vote
    1
    down vote

    favorite












    I have noticed this question https://stackoverflow.com/questions/49389392/nested-classes-and-collections-vba in StackOverflow, but it got closed faster than I could have answered. However, it was concerning a best practice for usage of collections of classes.



    I usually use Array and I add to it like this:



    Public Sub AddToTeam(emp As Employee)

    Dim cnt As Long
    cnt = UBound(pTeam)

    If Not pTeam(0) Is Nothing Then
    ReDim Preserve pTeam(cnt + 1)
    cnt = cnt + 1
    End If
    Set pTeam(cnt) = emp

    End Sub


    Furthermore, I initialize the Array like this in the class:



    Private Sub Class_Initialize()
    ReDim pTeam(0)
    End Sub



    This is the working code, feel free to give any ideas:



    Main Module



    Option Explicit

    Public Sub TestMe()

    Dim EmpA As New Employee
    Dim EmpB As New Employee
    Dim ManA As New Employee
    Dim TeamA As New Team

    ManA.Name = "John Doe Top Manager"
    EmpA.Name = "Peter"
    EmpB.Name = "George"

    Set EmpB.Manager = ManA
    TeamA.Name = "The best team!"

    TeamA.AddToTeam ManA
    TeamA.AddToTeam EmpA
    TeamA.AddToTeam EmpB

    TeamA.PrintNames
    Debug.Print TeamA.Name
    TeamA.PrintInfoForManagers

    End Sub


    Class Employee



    Option Explicit

    Private pName As String
    Private pManager As Employee
    Private pAge As Long
    Private pTeam As String
    Private pHasManager As Boolean

    Public Property Get HasManager() As Boolean
    HasManager = pHasManager
    End Property

    Public Property Let HasManager(Value As Boolean)
    pHasManager = Value
    End Property

    Public Property Get Manager() As Employee
    Set Manager = pManager
    End Property

    Public Property Set Manager(Value As Employee)
    Set pManager = Value
    HasManager = True
    End Property

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

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

    Public Property Get Team() As Employee
    Team = pTeam
    End Property

    Public Property Let Team(Value As Employee)
    pTeam = Value
    End Property


    Class Team



    Option Explicit

    Private pTeam() As Employee
    Private pName As String

    Public Sub PrintInfoForManagers()

    Dim emp As Variant
    For Each emp In pTeam
    If emp.HasManager Then
    Debug.Print emp.Name & " is managed by " & emp.Manager.Name
    Else
    Debug.Print emp.Name & " has no manager."
    End If
    Next emp

    End Sub

    Public Sub PrintNames()

    Dim emp As Variant
    For Each emp In pTeam
    Debug.Print emp.Name
    Next emp

    End Sub

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

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

    Public Sub AddToTeam(emp As Employee)

    Dim cnt As Long
    cnt = UBound(pTeam)

    If Not pTeam(0) Is Nothing Then
    ReDim Preserve pTeam(cnt + 1)
    cnt = cnt + 1
    End If
    Set pTeam(cnt) = emp

    End Sub

    Private Sub Class_Initialize()
    ReDim pTeam(0)
    End Sub



    So how would you review/improve it?







    share|improve this question























      up vote
      1
      down vote

      favorite









      up vote
      1
      down vote

      favorite











      I have noticed this question https://stackoverflow.com/questions/49389392/nested-classes-and-collections-vba in StackOverflow, but it got closed faster than I could have answered. However, it was concerning a best practice for usage of collections of classes.



      I usually use Array and I add to it like this:



      Public Sub AddToTeam(emp As Employee)

      Dim cnt As Long
      cnt = UBound(pTeam)

      If Not pTeam(0) Is Nothing Then
      ReDim Preserve pTeam(cnt + 1)
      cnt = cnt + 1
      End If
      Set pTeam(cnt) = emp

      End Sub


      Furthermore, I initialize the Array like this in the class:



      Private Sub Class_Initialize()
      ReDim pTeam(0)
      End Sub



      This is the working code, feel free to give any ideas:



      Main Module



      Option Explicit

      Public Sub TestMe()

      Dim EmpA As New Employee
      Dim EmpB As New Employee
      Dim ManA As New Employee
      Dim TeamA As New Team

      ManA.Name = "John Doe Top Manager"
      EmpA.Name = "Peter"
      EmpB.Name = "George"

      Set EmpB.Manager = ManA
      TeamA.Name = "The best team!"

      TeamA.AddToTeam ManA
      TeamA.AddToTeam EmpA
      TeamA.AddToTeam EmpB

      TeamA.PrintNames
      Debug.Print TeamA.Name
      TeamA.PrintInfoForManagers

      End Sub


      Class Employee



      Option Explicit

      Private pName As String
      Private pManager As Employee
      Private pAge As Long
      Private pTeam As String
      Private pHasManager As Boolean

      Public Property Get HasManager() As Boolean
      HasManager = pHasManager
      End Property

      Public Property Let HasManager(Value As Boolean)
      pHasManager = Value
      End Property

      Public Property Get Manager() As Employee
      Set Manager = pManager
      End Property

      Public Property Set Manager(Value As Employee)
      Set pManager = Value
      HasManager = True
      End Property

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

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

      Public Property Get Team() As Employee
      Team = pTeam
      End Property

      Public Property Let Team(Value As Employee)
      pTeam = Value
      End Property


      Class Team



      Option Explicit

      Private pTeam() As Employee
      Private pName As String

      Public Sub PrintInfoForManagers()

      Dim emp As Variant
      For Each emp In pTeam
      If emp.HasManager Then
      Debug.Print emp.Name & " is managed by " & emp.Manager.Name
      Else
      Debug.Print emp.Name & " has no manager."
      End If
      Next emp

      End Sub

      Public Sub PrintNames()

      Dim emp As Variant
      For Each emp In pTeam
      Debug.Print emp.Name
      Next emp

      End Sub

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

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

      Public Sub AddToTeam(emp As Employee)

      Dim cnt As Long
      cnt = UBound(pTeam)

      If Not pTeam(0) Is Nothing Then
      ReDim Preserve pTeam(cnt + 1)
      cnt = cnt + 1
      End If
      Set pTeam(cnt) = emp

      End Sub

      Private Sub Class_Initialize()
      ReDim pTeam(0)
      End Sub



      So how would you review/improve it?







      share|improve this question













      I have noticed this question https://stackoverflow.com/questions/49389392/nested-classes-and-collections-vba in StackOverflow, but it got closed faster than I could have answered. However, it was concerning a best practice for usage of collections of classes.



      I usually use Array and I add to it like this:



      Public Sub AddToTeam(emp As Employee)

      Dim cnt As Long
      cnt = UBound(pTeam)

      If Not pTeam(0) Is Nothing Then
      ReDim Preserve pTeam(cnt + 1)
      cnt = cnt + 1
      End If
      Set pTeam(cnt) = emp

      End Sub


      Furthermore, I initialize the Array like this in the class:



      Private Sub Class_Initialize()
      ReDim pTeam(0)
      End Sub



      This is the working code, feel free to give any ideas:



      Main Module



      Option Explicit

      Public Sub TestMe()

      Dim EmpA As New Employee
      Dim EmpB As New Employee
      Dim ManA As New Employee
      Dim TeamA As New Team

      ManA.Name = "John Doe Top Manager"
      EmpA.Name = "Peter"
      EmpB.Name = "George"

      Set EmpB.Manager = ManA
      TeamA.Name = "The best team!"

      TeamA.AddToTeam ManA
      TeamA.AddToTeam EmpA
      TeamA.AddToTeam EmpB

      TeamA.PrintNames
      Debug.Print TeamA.Name
      TeamA.PrintInfoForManagers

      End Sub


      Class Employee



      Option Explicit

      Private pName As String
      Private pManager As Employee
      Private pAge As Long
      Private pTeam As String
      Private pHasManager As Boolean

      Public Property Get HasManager() As Boolean
      HasManager = pHasManager
      End Property

      Public Property Let HasManager(Value As Boolean)
      pHasManager = Value
      End Property

      Public Property Get Manager() As Employee
      Set Manager = pManager
      End Property

      Public Property Set Manager(Value As Employee)
      Set pManager = Value
      HasManager = True
      End Property

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

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

      Public Property Get Team() As Employee
      Team = pTeam
      End Property

      Public Property Let Team(Value As Employee)
      pTeam = Value
      End Property


      Class Team



      Option Explicit

      Private pTeam() As Employee
      Private pName As String

      Public Sub PrintInfoForManagers()

      Dim emp As Variant
      For Each emp In pTeam
      If emp.HasManager Then
      Debug.Print emp.Name & " is managed by " & emp.Manager.Name
      Else
      Debug.Print emp.Name & " has no manager."
      End If
      Next emp

      End Sub

      Public Sub PrintNames()

      Dim emp As Variant
      For Each emp In pTeam
      Debug.Print emp.Name
      Next emp

      End Sub

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

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

      Public Sub AddToTeam(emp As Employee)

      Dim cnt As Long
      cnt = UBound(pTeam)

      If Not pTeam(0) Is Nothing Then
      ReDim Preserve pTeam(cnt + 1)
      cnt = cnt + 1
      End If
      Set pTeam(cnt) = emp

      End Sub

      Private Sub Class_Initialize()
      ReDim pTeam(0)
      End Sub



      So how would you review/improve it?









      share|improve this question












      share|improve this question




      share|improve this question








      edited Mar 21 at 9:37
























      asked Mar 20 at 17:18









      Vityata

      167115




      167115




















          2 Answers
          2






          active

          oldest

          votes

















          up vote
          1
          down vote



          accepted










          I prefer not to initiate arrays until they're needed. Automatically initiating your array can make it difficult to determine whether the array has been used. Using the Team class as an example, calling PrintNames before any names are added will throw an error.



          Private Sub Class_Initialize()
          ReDim pTeam(0)
          End Sub


          The main reason this is done is to avoid throwing an error when checking to whether or not an array has been initialized.



          There is a trick to avoid the error 9, Subscript out of range. Checking to see if an array IsEmpty() on an uninitialized array will cause UBound() to return -1.



          By using Call IsEmpty(pTeam) instead of ReDim pTeam(0) we can easily check whether the array has actually been used using If UBound(pTeam) = -1 Then.



          Private Sub Class_Initialize()
          Call IsEmpty(pTeam)
          End Sub

          Public Sub AddToTeam(emp As Employee)
          If UBound(pTeam) = -1 Then
          ReDim pTeam(0)
          Else
          ReDim Preserve pTeam(UBound(pTeam) + 1)
          End If
          Set pTeam(UBound(pTeam)) = emp
          End Sub


          Alternatively, we could write a function to check if the array has been used.



          Public Function hasMembers()
          Call IsEmpty(pTeam)
          hasMembers = UBound(pTeam) > -1
          End Function


          Using a Private Type to Reference Class Members



          Mathieu Guindon (formally known as Mat's Mug) likes to use a User Defined Type (UDT) named This to reference class level variables. I have adopted the technique but name it vars instead of This. In this way you can avoid having to the class variable names different from the parameter names (see code below).



          Refactored Code



          Employee:Class



          Option Explicit

          Private Type Variables
          Name As String
          Manager As Employee
          Age As Long
          Team As String
          End Type

          Private vars As Variables

          Public Function HasManager() As Boolean
          HasManager = Not vars.Manager Is Nothing
          End Function

          Public Property Get Manager() As Employee
          Set Manager = vars.Manager
          End Property

          Public Property Set Manager(Value As Employee)
          Set vars.Manager = Value
          End Property

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Property Get Team() As Employee
          Team = vars.Team
          End Property

          Public Property Let Team(Value As Employee)
          vars.Team = Value
          End Property

          Public Function This() As Team
          Set This = Me
          End Function


          Team:Class



          Option Explicit

          Private Type Variables
          Team() As Employee
          Name As String
          End Type
          Private vars As Variables

          Public Sub PrintInfoForManagers()

          Dim emp As Variant
          For Each emp In vars.Team
          If emp.HasManager Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."
          End If
          Next emp

          End Sub

          Public Sub PrintNames()
          Dim emp As Variant
          If UBound(vars.Team) = -1 Then
          Debug.Print "There are no Names to Print"
          Else
          For Each emp In vars.Team
          Debug.Print emp.Name
          Next emp
          End If

          End Sub

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Sub AddToTeam(emp As Employee)
          If Not Me.hasMembers Then
          ReDim vars.Team(0)
          Else
          ReDim Preserve vars.Team(UBound(vars.Team) + 1)
          End If
          Set vars.Team(UBound(vars.Team)) = emp
          End Sub

          Public Function hasMembers()
          Call IsEmpty(vars.Team)
          hasMembers = UBound(vars.Team) > -1
          End Function

          Public Function This() As Team
          Set This = Me
          End Function





          share|improve this answer























          • Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
            – Vityata
            Mar 21 at 9:29






          • 1




            Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
            – user109261
            Mar 21 at 9:43










          • Yup, I meant User Defined Types. :)
            – Vityata
            Mar 21 at 9:46

















          up vote
          2
          down vote













          I don't see the need for the HasManager property. Either a Manager is assigned, or not. It wouldn't make sense for you to set it to True when there isn't one, or False when there is.



          Then just change your Print function to check -



          If Not emp.Manager Is Nothing Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."


          If it was easy to overload the class initialization for employee, I'd say make a name value obligatory, but it's really not a simple task in VBA.




          These two properties:




          Public Property Get Team() As Employee
          Team = pTeam
          End Property

          Public Property Let Team(Value As Employee)
          pTeam = Value
          End Property



          Are passing Objects - why not pass Strings given the team information is handled by a string.






          share|improve this answer

















          • 1




            Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
            – Vityata
            Mar 21 at 9:23






          • 1




            Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
            – user109261
            Mar 21 at 9:28










          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190049%2fcollections-of-classes-in-vba%23new-answer', 'question_page');

          );

          Post as a guest






























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          1
          down vote



          accepted










          I prefer not to initiate arrays until they're needed. Automatically initiating your array can make it difficult to determine whether the array has been used. Using the Team class as an example, calling PrintNames before any names are added will throw an error.



          Private Sub Class_Initialize()
          ReDim pTeam(0)
          End Sub


          The main reason this is done is to avoid throwing an error when checking to whether or not an array has been initialized.



          There is a trick to avoid the error 9, Subscript out of range. Checking to see if an array IsEmpty() on an uninitialized array will cause UBound() to return -1.



          By using Call IsEmpty(pTeam) instead of ReDim pTeam(0) we can easily check whether the array has actually been used using If UBound(pTeam) = -1 Then.



          Private Sub Class_Initialize()
          Call IsEmpty(pTeam)
          End Sub

          Public Sub AddToTeam(emp As Employee)
          If UBound(pTeam) = -1 Then
          ReDim pTeam(0)
          Else
          ReDim Preserve pTeam(UBound(pTeam) + 1)
          End If
          Set pTeam(UBound(pTeam)) = emp
          End Sub


          Alternatively, we could write a function to check if the array has been used.



          Public Function hasMembers()
          Call IsEmpty(pTeam)
          hasMembers = UBound(pTeam) > -1
          End Function


          Using a Private Type to Reference Class Members



          Mathieu Guindon (formally known as Mat's Mug) likes to use a User Defined Type (UDT) named This to reference class level variables. I have adopted the technique but name it vars instead of This. In this way you can avoid having to the class variable names different from the parameter names (see code below).



          Refactored Code



          Employee:Class



          Option Explicit

          Private Type Variables
          Name As String
          Manager As Employee
          Age As Long
          Team As String
          End Type

          Private vars As Variables

          Public Function HasManager() As Boolean
          HasManager = Not vars.Manager Is Nothing
          End Function

          Public Property Get Manager() As Employee
          Set Manager = vars.Manager
          End Property

          Public Property Set Manager(Value As Employee)
          Set vars.Manager = Value
          End Property

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Property Get Team() As Employee
          Team = vars.Team
          End Property

          Public Property Let Team(Value As Employee)
          vars.Team = Value
          End Property

          Public Function This() As Team
          Set This = Me
          End Function


          Team:Class



          Option Explicit

          Private Type Variables
          Team() As Employee
          Name As String
          End Type
          Private vars As Variables

          Public Sub PrintInfoForManagers()

          Dim emp As Variant
          For Each emp In vars.Team
          If emp.HasManager Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."
          End If
          Next emp

          End Sub

          Public Sub PrintNames()
          Dim emp As Variant
          If UBound(vars.Team) = -1 Then
          Debug.Print "There are no Names to Print"
          Else
          For Each emp In vars.Team
          Debug.Print emp.Name
          Next emp
          End If

          End Sub

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Sub AddToTeam(emp As Employee)
          If Not Me.hasMembers Then
          ReDim vars.Team(0)
          Else
          ReDim Preserve vars.Team(UBound(vars.Team) + 1)
          End If
          Set vars.Team(UBound(vars.Team)) = emp
          End Sub

          Public Function hasMembers()
          Call IsEmpty(vars.Team)
          hasMembers = UBound(vars.Team) > -1
          End Function

          Public Function This() As Team
          Set This = Me
          End Function





          share|improve this answer























          • Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
            – Vityata
            Mar 21 at 9:29






          • 1




            Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
            – user109261
            Mar 21 at 9:43










          • Yup, I meant User Defined Types. :)
            – Vityata
            Mar 21 at 9:46














          up vote
          1
          down vote



          accepted










          I prefer not to initiate arrays until they're needed. Automatically initiating your array can make it difficult to determine whether the array has been used. Using the Team class as an example, calling PrintNames before any names are added will throw an error.



          Private Sub Class_Initialize()
          ReDim pTeam(0)
          End Sub


          The main reason this is done is to avoid throwing an error when checking to whether or not an array has been initialized.



          There is a trick to avoid the error 9, Subscript out of range. Checking to see if an array IsEmpty() on an uninitialized array will cause UBound() to return -1.



          By using Call IsEmpty(pTeam) instead of ReDim pTeam(0) we can easily check whether the array has actually been used using If UBound(pTeam) = -1 Then.



          Private Sub Class_Initialize()
          Call IsEmpty(pTeam)
          End Sub

          Public Sub AddToTeam(emp As Employee)
          If UBound(pTeam) = -1 Then
          ReDim pTeam(0)
          Else
          ReDim Preserve pTeam(UBound(pTeam) + 1)
          End If
          Set pTeam(UBound(pTeam)) = emp
          End Sub


          Alternatively, we could write a function to check if the array has been used.



          Public Function hasMembers()
          Call IsEmpty(pTeam)
          hasMembers = UBound(pTeam) > -1
          End Function


          Using a Private Type to Reference Class Members



          Mathieu Guindon (formally known as Mat's Mug) likes to use a User Defined Type (UDT) named This to reference class level variables. I have adopted the technique but name it vars instead of This. In this way you can avoid having to the class variable names different from the parameter names (see code below).



          Refactored Code



          Employee:Class



          Option Explicit

          Private Type Variables
          Name As String
          Manager As Employee
          Age As Long
          Team As String
          End Type

          Private vars As Variables

          Public Function HasManager() As Boolean
          HasManager = Not vars.Manager Is Nothing
          End Function

          Public Property Get Manager() As Employee
          Set Manager = vars.Manager
          End Property

          Public Property Set Manager(Value As Employee)
          Set vars.Manager = Value
          End Property

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Property Get Team() As Employee
          Team = vars.Team
          End Property

          Public Property Let Team(Value As Employee)
          vars.Team = Value
          End Property

          Public Function This() As Team
          Set This = Me
          End Function


          Team:Class



          Option Explicit

          Private Type Variables
          Team() As Employee
          Name As String
          End Type
          Private vars As Variables

          Public Sub PrintInfoForManagers()

          Dim emp As Variant
          For Each emp In vars.Team
          If emp.HasManager Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."
          End If
          Next emp

          End Sub

          Public Sub PrintNames()
          Dim emp As Variant
          If UBound(vars.Team) = -1 Then
          Debug.Print "There are no Names to Print"
          Else
          For Each emp In vars.Team
          Debug.Print emp.Name
          Next emp
          End If

          End Sub

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Sub AddToTeam(emp As Employee)
          If Not Me.hasMembers Then
          ReDim vars.Team(0)
          Else
          ReDim Preserve vars.Team(UBound(vars.Team) + 1)
          End If
          Set vars.Team(UBound(vars.Team)) = emp
          End Sub

          Public Function hasMembers()
          Call IsEmpty(vars.Team)
          hasMembers = UBound(vars.Team) > -1
          End Function

          Public Function This() As Team
          Set This = Me
          End Function





          share|improve this answer























          • Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
            – Vityata
            Mar 21 at 9:29






          • 1




            Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
            – user109261
            Mar 21 at 9:43










          • Yup, I meant User Defined Types. :)
            – Vityata
            Mar 21 at 9:46












          up vote
          1
          down vote



          accepted







          up vote
          1
          down vote



          accepted






          I prefer not to initiate arrays until they're needed. Automatically initiating your array can make it difficult to determine whether the array has been used. Using the Team class as an example, calling PrintNames before any names are added will throw an error.



          Private Sub Class_Initialize()
          ReDim pTeam(0)
          End Sub


          The main reason this is done is to avoid throwing an error when checking to whether or not an array has been initialized.



          There is a trick to avoid the error 9, Subscript out of range. Checking to see if an array IsEmpty() on an uninitialized array will cause UBound() to return -1.



          By using Call IsEmpty(pTeam) instead of ReDim pTeam(0) we can easily check whether the array has actually been used using If UBound(pTeam) = -1 Then.



          Private Sub Class_Initialize()
          Call IsEmpty(pTeam)
          End Sub

          Public Sub AddToTeam(emp As Employee)
          If UBound(pTeam) = -1 Then
          ReDim pTeam(0)
          Else
          ReDim Preserve pTeam(UBound(pTeam) + 1)
          End If
          Set pTeam(UBound(pTeam)) = emp
          End Sub


          Alternatively, we could write a function to check if the array has been used.



          Public Function hasMembers()
          Call IsEmpty(pTeam)
          hasMembers = UBound(pTeam) > -1
          End Function


          Using a Private Type to Reference Class Members



          Mathieu Guindon (formally known as Mat's Mug) likes to use a User Defined Type (UDT) named This to reference class level variables. I have adopted the technique but name it vars instead of This. In this way you can avoid having to the class variable names different from the parameter names (see code below).



          Refactored Code



          Employee:Class



          Option Explicit

          Private Type Variables
          Name As String
          Manager As Employee
          Age As Long
          Team As String
          End Type

          Private vars As Variables

          Public Function HasManager() As Boolean
          HasManager = Not vars.Manager Is Nothing
          End Function

          Public Property Get Manager() As Employee
          Set Manager = vars.Manager
          End Property

          Public Property Set Manager(Value As Employee)
          Set vars.Manager = Value
          End Property

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Property Get Team() As Employee
          Team = vars.Team
          End Property

          Public Property Let Team(Value As Employee)
          vars.Team = Value
          End Property

          Public Function This() As Team
          Set This = Me
          End Function


          Team:Class



          Option Explicit

          Private Type Variables
          Team() As Employee
          Name As String
          End Type
          Private vars As Variables

          Public Sub PrintInfoForManagers()

          Dim emp As Variant
          For Each emp In vars.Team
          If emp.HasManager Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."
          End If
          Next emp

          End Sub

          Public Sub PrintNames()
          Dim emp As Variant
          If UBound(vars.Team) = -1 Then
          Debug.Print "There are no Names to Print"
          Else
          For Each emp In vars.Team
          Debug.Print emp.Name
          Next emp
          End If

          End Sub

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Sub AddToTeam(emp As Employee)
          If Not Me.hasMembers Then
          ReDim vars.Team(0)
          Else
          ReDim Preserve vars.Team(UBound(vars.Team) + 1)
          End If
          Set vars.Team(UBound(vars.Team)) = emp
          End Sub

          Public Function hasMembers()
          Call IsEmpty(vars.Team)
          hasMembers = UBound(vars.Team) > -1
          End Function

          Public Function This() As Team
          Set This = Me
          End Function





          share|improve this answer















          I prefer not to initiate arrays until they're needed. Automatically initiating your array can make it difficult to determine whether the array has been used. Using the Team class as an example, calling PrintNames before any names are added will throw an error.



          Private Sub Class_Initialize()
          ReDim pTeam(0)
          End Sub


          The main reason this is done is to avoid throwing an error when checking to whether or not an array has been initialized.



          There is a trick to avoid the error 9, Subscript out of range. Checking to see if an array IsEmpty() on an uninitialized array will cause UBound() to return -1.



          By using Call IsEmpty(pTeam) instead of ReDim pTeam(0) we can easily check whether the array has actually been used using If UBound(pTeam) = -1 Then.



          Private Sub Class_Initialize()
          Call IsEmpty(pTeam)
          End Sub

          Public Sub AddToTeam(emp As Employee)
          If UBound(pTeam) = -1 Then
          ReDim pTeam(0)
          Else
          ReDim Preserve pTeam(UBound(pTeam) + 1)
          End If
          Set pTeam(UBound(pTeam)) = emp
          End Sub


          Alternatively, we could write a function to check if the array has been used.



          Public Function hasMembers()
          Call IsEmpty(pTeam)
          hasMembers = UBound(pTeam) > -1
          End Function


          Using a Private Type to Reference Class Members



          Mathieu Guindon (formally known as Mat's Mug) likes to use a User Defined Type (UDT) named This to reference class level variables. I have adopted the technique but name it vars instead of This. In this way you can avoid having to the class variable names different from the parameter names (see code below).



          Refactored Code



          Employee:Class



          Option Explicit

          Private Type Variables
          Name As String
          Manager As Employee
          Age As Long
          Team As String
          End Type

          Private vars As Variables

          Public Function HasManager() As Boolean
          HasManager = Not vars.Manager Is Nothing
          End Function

          Public Property Get Manager() As Employee
          Set Manager = vars.Manager
          End Property

          Public Property Set Manager(Value As Employee)
          Set vars.Manager = Value
          End Property

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Property Get Team() As Employee
          Team = vars.Team
          End Property

          Public Property Let Team(Value As Employee)
          vars.Team = Value
          End Property

          Public Function This() As Team
          Set This = Me
          End Function


          Team:Class



          Option Explicit

          Private Type Variables
          Team() As Employee
          Name As String
          End Type
          Private vars As Variables

          Public Sub PrintInfoForManagers()

          Dim emp As Variant
          For Each emp In vars.Team
          If emp.HasManager Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."
          End If
          Next emp

          End Sub

          Public Sub PrintNames()
          Dim emp As Variant
          If UBound(vars.Team) = -1 Then
          Debug.Print "There are no Names to Print"
          Else
          For Each emp In vars.Team
          Debug.Print emp.Name
          Next emp
          End If

          End Sub

          Public Property Get Name() As String
          Name = vars.Name
          End Property

          Public Property Let Name(Value As String)
          vars.Name = Value
          End Property

          Public Sub AddToTeam(emp As Employee)
          If Not Me.hasMembers Then
          ReDim vars.Team(0)
          Else
          ReDim Preserve vars.Team(UBound(vars.Team) + 1)
          End If
          Set vars.Team(UBound(vars.Team)) = emp
          End Sub

          Public Function hasMembers()
          Call IsEmpty(vars.Team)
          hasMembers = UBound(vars.Team) > -1
          End Function

          Public Function This() As Team
          Set This = Me
          End Function






          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Mar 21 at 9:44


























          answered Mar 21 at 9:21







          user109261


















          • Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
            – Vityata
            Mar 21 at 9:29






          • 1




            Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
            – user109261
            Mar 21 at 9:43










          • Yup, I meant User Defined Types. :)
            – Vityata
            Mar 21 at 9:46
















          • Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
            – Vityata
            Mar 21 at 9:29






          • 1




            Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
            – user109261
            Mar 21 at 9:43










          • Yup, I meant User Defined Types. :)
            – Vityata
            Mar 21 at 9:46















          Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
          – Vityata
          Mar 21 at 9:29




          Interesting ideas, thanks, Class_Initialize really somehow smells. In general, why are you using Call? I thought this was one of the "forbidden" VBA features, just like Integer. A separate Thumbs Up for the Enums. It looks a bit clearer than the automatic pVariable from MZ-Code.
          – Vityata
          Mar 21 at 9:29




          1




          1




          Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
          – user109261
          Mar 21 at 9:43




          Call IsEmpty (vars.Team) looks more natural then IsEmpty vars.Team to me. Although there is no speed advantage to using Integer instead of Long, I would not call Integer a "forbidden" fracture. You mean User Defined Typesnot Enums but thanks!
          – user109261
          Mar 21 at 9:43












          Yup, I meant User Defined Types. :)
          – Vityata
          Mar 21 at 9:46




          Yup, I meant User Defined Types. :)
          – Vityata
          Mar 21 at 9:46












          up vote
          2
          down vote













          I don't see the need for the HasManager property. Either a Manager is assigned, or not. It wouldn't make sense for you to set it to True when there isn't one, or False when there is.



          Then just change your Print function to check -



          If Not emp.Manager Is Nothing Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."


          If it was easy to overload the class initialization for employee, I'd say make a name value obligatory, but it's really not a simple task in VBA.




          These two properties:




          Public Property Get Team() As Employee
          Team = pTeam
          End Property

          Public Property Let Team(Value As Employee)
          pTeam = Value
          End Property



          Are passing Objects - why not pass Strings given the team information is handled by a string.






          share|improve this answer

















          • 1




            Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
            – Vityata
            Mar 21 at 9:23






          • 1




            Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
            – user109261
            Mar 21 at 9:28














          up vote
          2
          down vote













          I don't see the need for the HasManager property. Either a Manager is assigned, or not. It wouldn't make sense for you to set it to True when there isn't one, or False when there is.



          Then just change your Print function to check -



          If Not emp.Manager Is Nothing Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."


          If it was easy to overload the class initialization for employee, I'd say make a name value obligatory, but it's really not a simple task in VBA.




          These two properties:




          Public Property Get Team() As Employee
          Team = pTeam
          End Property

          Public Property Let Team(Value As Employee)
          pTeam = Value
          End Property



          Are passing Objects - why not pass Strings given the team information is handled by a string.






          share|improve this answer

















          • 1




            Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
            – Vityata
            Mar 21 at 9:23






          • 1




            Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
            – user109261
            Mar 21 at 9:28












          up vote
          2
          down vote










          up vote
          2
          down vote









          I don't see the need for the HasManager property. Either a Manager is assigned, or not. It wouldn't make sense for you to set it to True when there isn't one, or False when there is.



          Then just change your Print function to check -



          If Not emp.Manager Is Nothing Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."


          If it was easy to overload the class initialization for employee, I'd say make a name value obligatory, but it's really not a simple task in VBA.




          These two properties:




          Public Property Get Team() As Employee
          Team = pTeam
          End Property

          Public Property Let Team(Value As Employee)
          pTeam = Value
          End Property



          Are passing Objects - why not pass Strings given the team information is handled by a string.






          share|improve this answer













          I don't see the need for the HasManager property. Either a Manager is assigned, or not. It wouldn't make sense for you to set it to True when there isn't one, or False when there is.



          Then just change your Print function to check -



          If Not emp.Manager Is Nothing Then
          Debug.Print emp.Name & " is managed by " & emp.Manager.Name
          Else
          Debug.Print emp.Name & " has no manager."


          If it was easy to overload the class initialization for employee, I'd say make a name value obligatory, but it's really not a simple task in VBA.




          These two properties:




          Public Property Get Team() As Employee
          Team = pTeam
          End Property

          Public Property Let Team(Value As Employee)
          pTeam = Value
          End Property



          Are passing Objects - why not pass Strings given the team information is handled by a string.







          share|improve this answer













          share|improve this answer



          share|improve this answer











          answered Mar 20 at 21:40









          Raystafarian

          5,4331046




          5,4331046







          • 1




            Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
            – Vityata
            Mar 21 at 9:23






          • 1




            Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
            – user109261
            Mar 21 at 9:28












          • 1




            Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
            – Vityata
            Mar 21 at 9:23






          • 1




            Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
            – user109261
            Mar 21 at 9:28







          1




          1




          Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
          – Vityata
          Mar 21 at 9:23




          Both are good ideas, thanks. However, I like the HasManager, it somehow gives me some kind of an interface to work with. Not Is Nothing is a good way around. Team is assigned as employee, because I can use some other properties later (probably).
          – Vityata
          Mar 21 at 9:23




          1




          1




          Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
          – user109261
          Mar 21 at 9:28




          Passing string Names for the managers instead of Employee objects defeats the purpose of the post. Consider that you add and email field to the Employee class and needed to retrieve the Email address of an employees manager. Currently you could simply use Employee.Manager.Email . If you use string then you would have to iterate over all the managers to find the Email address.
          – user109261
          Mar 21 at 9:28












           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f190049%2fcollections-of-classes-in-vba%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation