Cloning Class Instances

I’m working on a project that requires me to make multiple copies of a custom object. That generally means I make a Duplicate method and assign each property of the source instance to the copy instance. It’s not terrible, but I just have to be careful that when I change the properties of the class that I update the Duplicate method. I decided that I would see what other options are available and I found this StackOverflow answer that I think is interesting. So I tried it.

Instead of declaring a bunch of private variables in the class and all the getters and setters for those variables, all of the property values will be in a user-defined type. The UDT variable will be private to the class, but the actual UDT has to be in a standard module because VBA won’t let you put types in a class module. Now when I want to clone that class instance, I can pass the whole UDT variable. Unlike a class that passes the reference, the UDT makes a copy of all of the values in memory so you end up with two separate structures in memory – just what I want for a clone.

I made a CContact class that will have a FirstName, LastName, and LastContact property. The poorly named LastContact property is the date last contacted. I wanted to put at least two different data types (String and Date) in the class for demonstration purposes. The declaration of CContact looks like this:

Private mlContactID As Long
Private mtContactMemento As ContactMemento

I only have two private variables; ContactID that never want to copy and a ContactMomento variable that contains all the data that I do want to copy. I keep the ContactID getter and setter the same as I normally would, but the mtContactMomento property statements are a little different. First, let’s look at the ContactMomento type. This is in my MGlobals standard module.

Public Type ContactMemento
FirstName As String
LastName As String
LastContact As Date
End Type

I named the type ObjectName + Memento. Memento is from the Memento pattern, which is typically used to store a previous state for Undo purposes, although we’re not really using it like that here. The UDT holds all of the properties that I want to copy to a new class instance. Back in the class, the typical getters and setters look like this:

Public Property Let FirstName(ByVal sFirstName As String)

mtContactMemento.FirstName = sFirstName

End Property

Public Property Get FirstName() As String

FirstName = mtContactMemento.FirstName

End Property

That’s pretty much the same as typical property statements except I’m pulling the data from my private ContactMemento variable instead of individual variables for each project. With that all set up, this works just like a class that I would create normally – not using a UDT. The last bit of setup that I need is property statements for the ContactMemento as a whole.

Public Property Let ContactMemento(tContactMemento As ContactMemento)

mtContactMemento = tContactMemento

End Property

Public Property Get ContactMemento() As ContactMemento

ContactMemento = mtContactMemento

End Property

And then I need a way to clone the class, so I create a Clone property.

Public Property Get Clone() As CContact

Dim clsReturn As CContact

Set clsReturn = New CContact
clsReturn.ContactMemento = Me.ContactMemento

Set Clone = clsReturn

End Property

By assigning the ContactMemento from the source instance to the class instance, all the properties I want to copy are done so in one shot rather than one at a time. If I add any properties to the class, I don’t have to adjust the Clone property, which means that I won’t forget and miss a property. Let’s test it out. If create one instance, then clone it and change the LastContact property just for some variety.

Sub FillClass()

Dim clsContact As CContact

Set gclsContacts = New CContacts

Set clsContact = New CContact
With clsContact
.FirstName = "Dick"
.LastName = "Kusleika"
.LastContact = Date
End With
gclsContacts.Add clsContact

End Sub

Sub CloneClass()

Dim clsClone As CContact
Dim clsSource As CContact
Dim clsContact As CContact

FillClass

Set clsSource = gclsContacts.Contact(1)
Set clsClone = clsSource.Clone
clsClone.LastContact = Date + 1
gclsContacts.Add clsClone

For Each clsContact In gclsContacts
With clsContact
Debug.Print .ContactID, .FirstName, .LastName, .LastContact
End With
Next clsContact

End Sub

That outputs:

I like a system that doesn’t rely on me being careful to update a Clone or Duplicate method. But there are some things I don’t like too. With this method, my class is not longer self-contained. The UDT declaration has to live in a standard module. If I copy my class module to another project, it won’t compile until I declare the UDT in the new project. I would also have to change all of my code generation stuff to use a UDT rather than individual private variables. In all the code I’ve written, I’ve probably only had to clone class instances about a half dozen times, so it’s probably not worth it.

You can download CloneClass.zip

9 thoughts on “Cloning Class Instances

  1. Dick, To keep it all in the class, could you use a collection in place of the UDT? That would also handle your mixed data types. The collection items and keys would be analogous to the UDT values and member names. I think.

    Anyways, great post. I didn’t know about the Memento pattern before.

  2. I like Dick’s approach despite the dependency on the externally declared UDT.
    Using a Collection would result in unwieldy referencing.

  3. Contact memento could be a dictionary, or even better a custom class that is a dictionary,
    This contactmemento class will have a duplicate method, that will run through the dictionary and create a copy of its elements. This way you have iecapsulated away from the contact object the varying parameters, you will have a specialised class for them, which will take care of duplication, validation and so on.
    Also you don’t need to expand the user defined type, just add a new key to the class and evening else works

  4. Unfortunately this approach only seems to work for simple types. I tried cloning a memento containing a Collection object, but it did not copy the values stored in the collection.

  5. To get the same results I’d prefer
    <lang=”vb”>Sub M_snb()
    Set c_00 = CreateObject(“scripting.dictionary”)

    c_00.Add “firstname”, “dddd”
    c_00.Add “lastname”, “eee”
    c_00.Add “date”, Date

    MsgBox c_00(“firstname”) & vbLf & c_00(“lastname”) & vbLf & c_00(“date”)

    Set c_01 = c_00
    c_01(“date”) = c_01(“date”) + 1

    MsgBox c_01(“firstname”) & vbLf & c_01(“lastname”) & vbLf & c_01(“date”)
    End Sub

    That will even ‘clone’ a dictionary or collection in the dictionary.

  6. Sub M_snb()
    Set c_00 = CreateObject(“scripting.dictionary”)

       c_00.Add "firstname", "dddd"
       c_00.Add "lastname", "eee"
       c_00.Add "date", Date
    
       MsgBox c_00("firstname") & vbLf & c_00("lastname") & vbLf & c_00("date")
    
       Set c_01 = c_00
    
       c_01("date") = c_01("date") + 1
       MsgBox c_01("firstname") & vbLf & c_01("lastname") & vbLf & c_01("date")
    
    End Sub
    
  7. @snb I must be missing something. Your code creates two references (c_00 and c_01) to the same object in memory. Any changes to c_01 will change c_00. The whole point is to create independent objects.

Leave a Reply

Your email address will not be published. Required fields are marked *