29

I am attempting to create two identical objects in VB6 by assignment statements; something like this...

Dim myobj1 As Class1
Dim myobj2 As Class1

Set myobj1 = New Class1
myobj1.myval = 1
Set myobj2 = myobj1

It has become apparent that this doesn't create two objects but rather two references to the same object, which isn't what I am after. Is there any way to create a second object in this sort of way, or do I have to copy the object one member at a time...

Set myobj2 = new Class1
myobj2.mem1 = myobj1.mem1
...

?

Edit 2 Scott Whitlock has updated his excellent answer and I have incorporated his changes into this now-working code snippet.

Private Type MyMemento
     Value1 As Integer
     Value2 As String
End Type

Private Memento As MyMemento

Public Property Let myval(ByVal newval As Integer)
Memento.Value1 = newval
End Property

Public Property Get myval() As Integer
myval = Memento.Value1
End Property

Friend Property Let SetMemento(new_memento As MyMemento)
    Memento = new_memento
End Property

Public Function Copy() As Class1
     Dim Result As Class1
     Set Result = New Class1
     Result.SetMemento = Memento
     Set Copy = Result
End Function

One then performs the assignment in the code thus...

Set mysecondobj = myfirstobj.Copy
Brian Hooper
  • 21,544
  • 24
  • 88
  • 139
  • 1
    This question might be of interest: http://stackoverflow.com/questions/218696/cloning-objects-in-vba – Hans Olsson Jan 26 '11 at 14:29
  • 2
    I like how in the question of interest, the second best answer is just a reference to the answer of this question! :) – Vityata Feb 13 '19 at 11:09

3 Answers3

41

Like many modern languages, VB6 has value types and reference types. Classes define reference types. On the other hand, your basic types like Integer are value types.

The basic difference is in assignment:

Dim a as Integer
Dim b as Integer
a = 2
b = a
a = 1

The result is that a is 1 and b is 2. That's because assignment in value types makes a copy. That's because each variable has space allocated for the value on the stack (in the case of VB6, an Integer takes up 2 bytes on the stack).

For classes, it works differently:

Dim a as MyClass
Dim b as MyClass
Set a = New MyClass
a.Value1 = 2
Set b = a
a.Value1 = 1

The result is that both a.Value1 and b.Value1 are 1. That's because the state of the object is stored in the heap, not on the stack. Only the reference to the object is stored on the stack, so Set b = a overwrites the reference. Interestingly, VB6 is explicit about this by forcing you to use the Set keyword. Most other modern languages don't require this.

Now, you can create your own value types (in VB6 they're called User Defined Types, but in most other languages they're called structs or structures). Here's a tutorial.

The differences between a class and a user defined type (aside from a class being a reference type and a UDT being a value type) is that a class can contain behaviors (methods and properties) where a UDT cannot. If you're just looking for a record-type class, then a UDT may be your solution.

You can use a mix of these techniques. Let's say you need a Class because you have certain behaviors and calculations that you want to include along with the data. You can use the memento pattern to hold the state of an object inside of a UDT:

Type MyMemento
    Value1 As Integer
    Value2 As String
End Type

In your class, make sure that all your internal state is stored inside a private member of type MyMemento. Write your properties and methods so they only use data in that one private member variable.

Now making a copy of your object is simple. Just write a new method on your class called Copy() that returns a new instance of your class and initialize it with a copy of its own memento:

Private Memento As MyMemento

Friend Sub SetMemento(NewMemento As MyMemento)
    Memento = NewMemento
End Sub

Public Function Copy() as MyClass
    Dim Result as MyClass
    Set Result = new MyClass
    Call Result.SetMemento(Memento)
    Set Copy = Result
End Function

The Friend only hides it from stuff outside your project, so it doesn't do much to hide the SetMemento sub, but it's all you can do with VB6.

HTH

Scott Whitlock
  • 13,739
  • 7
  • 65
  • 114
  • 7
    +1 Cleanest Memento implementation I've seen in VBx, I never considered using a type as a shortcut. Well done that man! – Binary Worrier Jan 26 '11 at 14:49
  • @Binary Worrier - Thanks! I knew my l33t VB6 skills would come in handy some day... haha. :) – Scott Whitlock Jan 26 '11 at 15:20
  • thank you for your answer; I have been trying it out. I have had to make the `Type` definition `Private`, and drop the `Me.` off `Me.Memento`, but I cannot get the `Result.Memento` part to compile; it responds 'Method or data member not found'. The only way I can get this to compile is to make all the members public and assign them one at a time. Have I missed something somewhere? (Added my version of your code to the question.) – Brian Hooper Jan 26 '11 at 15:29
  • 1
    @Brian Hooper - Sorry, I got my .NET mixed with my VB6 again. You'll have to create a `SetMemento` method on your class. I'll update my answer. If you expose it as `Friend` you'll have to make your type definition `Friend` as well, I think. – Scott Whitlock Jan 26 '11 at 15:47
  • thank you, it is bowling along merrily now. I didn't find it necessary to make the `type` a `friend`. – Brian Hooper Jan 26 '11 at 16:11
  • this answer is similar to this http://stackoverflow.com/questions/580623/how-to-clone-an-object-in-vb6 – Smith Dec 21 '12 at 22:26
  • I cannot express how grateful I am for your solution... so many years passed and it's the way to implement. My 2 cents here is that if you have a class that uses another internally mostly to represent state - like **PrescriptionItem** is composed by **PrescriptionKey** and **PrescriptionPosology** - you can still use your trick: `Set Memento.KeyPrescription = NewMemento.KeyPrescription.Copy() Set Memento.Posology = NewMemento.Posology.Copy()` – Marcelo Scofano Diniz Aug 21 '20 at 04:21
3

@Scott Whitlock, I was not able to make your code work but if it works it would be great.

I've created a regular module where I put the memento type

Type MyMemento
    Value1 As Integer
    Value2 As String
End Type

Then I create a class module called MyClass with the code

Private Memento As MyMemento

Friend Sub SetMemento(NewMemento As MyMemento)
        Memento = NewMemento
End Sub

Public Function Copy() as MyClass
    Dim Result as MyClass
    Set Result = new MyClass
    Result.SetMemento(Memento)
    Set Copy = Result
End Function

Finally I try to call the copy function in another regular module like this

Sub Pruebas()
    Dim Primero As MyClass, segundo As MyClass
    Set Primero = New MyClass
    Set segundo = New MyClass
    Set segundo = Primero.Copy
End Sub

I get the message (below the picture): Error de compilacion: El tipo de agumento de ByRef no coincide

Here is an image (short of 10 points so here is the link): https://i.stack.imgur.com/KPdBR.gif

I was not able to get the message in English, I live in Spain.

Would you be so kind to provide with an example in VBA Excel?, I have been really trying to make this work.

Thanks for your work

===============================================

EDIT: Problem Solved:

The problem was on line "Result.SetMemento(Memento)", in VBA it needed to be called with "Call"

Public Function Copy() As MyClass
    Dim Result As MyClass
    Set Result = New MyClass
    Call Result.SetMemento(Memento)
    Set Copy = Result
End Function

It works great, thanks Scott Whitlock, you are a genius

verzulsan
  • 31
  • 2
  • 2
    Actually, you don't need the `Call` keyword, but then you need to remove the parentheses, like this: `Result.SetMemento Memento`, so you you can pass the variable `ByRef`. Calling it like `Result.SetMemento(Memento)` would force passing the variable `ByVal`. [Eric Lippert explains this here](https://blogs.msdn.microsoft.com/ericlippert/2003/09/15/what-do-you-mean-cannot-use-parentheses/) – Victor Moraes Mar 07 '17 at 16:10
2

or do I have to copy the object one member at a time...

Unfortunately yes.

It is possible (but technically very very difficult) to write a COM server in C++ that - using the IDispatch interface - will copy the value of each property, but really this is High Temple programming, if I had to do it, I don't I know if I could do it, but I'd be looking at something like 10 days work ( and I know how COM is implemented in C++, I'd also need to investigate to see if ATL framework has anything to help etc).

I worked with Vb3, 4,5 & 6 for something like 10 years (hands on, 5 days a week) and never found a good way to do this, beyond manually implementing serialisation patterns like Mementos and Save & Store, which really just boiled down to fancy ways of copying each member, one at a time.

Binary Worrier
  • 50,774
  • 20
  • 136
  • 184