Nothing Keyword Destories Objects rather than just resetting the variable to an empty variable

  • Thread starter Ronald R. Dodge, Jr.
  • Start date
R

Ronald R. Dodge, Jr.

Office XP, SP3 (VBA 6.0)
WIN XP, SP2


I have read over the text dealing with custom objects

According to the text that I have read online from MS themselves, the
keyword "NOTHING" should only destroy the object when there is no other
variable refering to that object. However, as I have learned the hard way,
that is not the case. Any time any variable is set to NOTHING, the object
is destroyed, so any object variable referencing to that object is then also
set to NOTHING.

Example:

PayCode collection has a set of objects containing properties. One of those
PayCode objects is passed onto an Employee Object. It's passed into the
employee object via a property with the object using the ByRef within the
signature line, so if there's any changes to the PayCode object, it's not
only changed within the PayCodes collection, but also within the Employee's
PayCode reference to it.

However, if the Employee Object is set to be destroyed, via the clean up
code, the code should be to set the object variable within the Employee
Object to NOTHING. However, doing that not only does that, but also set's
that particular object within the PayCodes collection to NOTHING as if the
object itself has been destroyed.

How do I resolve this issue, so as I can have it reset only the variable,
not destroy the actual object, but yet, also allow for changes to carry into
the variable when they are done into the main object?

--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
P

Peter T

Afraid I don't follow most of that, maybe a simple example might help.

One thing though, I think you are misunderstanding the difference of passing
an object byref & byval. Either way, any changes to the object's properties
will persist.

The only difference is with byval a new pointer to the original object is
created and passed. This means even if you destroy or reassign the byval
object within the procedure, the original object variable will continue
point to the same object.

Regards,
Peter T
 
R

Ronald R. Dodge, Jr.

The way I understood the ByRef and ByVal are the following:

ByRef - References to the object, and any changes done to the object is done
for any variable referencing to that object, rather it be done in the
original code or in the code referencing to the object.

ByVal - Creates a whole new replica of the object with all of the same
settings as the object that is being passed onto it, but any changes done to
the original object is not reflects in the new object, just like any changes
done in the new object is not reflected in the original object.

Though I haven't fully tested it to be sure with object variables, but
simple data type variables does work like this.

--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
P

Peter T

ByVal - Creates a whole new replica of the object with all of the same
settings as the object that is being passed onto it

No it doesn't. It creates a new (temporary) pointer to the original
intrinsic object.

As far as the original object's properties are concerned there is no
difference between using ByRef and ByVal.

Regards,
Peter T
 
R

Ronald R. Dodge, Jr.

Where does it state this pointer bit within the documentations? All I see
in the documentations are by references for ByRef and a copy of the variable
with ByVal looking at different places.

Also, how is it that I know it will work out the same way with my custom
objects similar to how things works out with the objects built into the
application itself such as Worksheet object as the Worksheet object is
directly tied to the WorkSheets collection object?

--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
R

Ronald R. Dodge, Jr.

As far as I can tell, what you are saying only works with intrinsic objects.

Given my custom objects are within a project, not part of the application
itself, but the very definition of intrinsic objects, my custom objects are
NOT intrinsic objects, thus your argument for how the ByVal works would not
work out.

--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
P

Peter T

Ronald R. Dodge said:
Where does it state this pointer bit within the documentations? All I see
in the documentations are by references for ByRef and a copy of the
variable with ByVal looking at different places.

Without searching I have no idea. Let me throw the question back at you -
where is something like the following documented:

" ByVal - Creates a whole new replica of the object with all of the same
settings as the object that is being passed onto it"

I'd be surprised if you find that anywhere.
Also, how is it that I know it will work out the same way with my custom
objects similar to how things works out with the objects built into the
application itself such as Worksheet object as the Worksheet object is
directly tied to the WorkSheets collection object?

I don't know anything about your custom obects but I would have thought you
are best placed to demonstrate for yourself.

Maybe the following will demonstrate that ByVal and ByRef work with the same
intrinsic object, and that in the case of ByVal merely a new pointer to the
same object is created.

Sub test4()
Dim c As Class1

Set c = New Class1
proc c, c

MsgBox c.x & vbCr & c.y & vbCr & ObjPtr(c) ' all same
End Sub

Sub proc(ByRef cr As Class1, ByVal cv As Class1)

cr.x = ObjPtr(cr)
cv.y = ObjPtr(cv)

Set cv = New Class1
cv.y = 123
MsgBox cv.y

End Sub

'' in class1
Public x As Long, y As Long

Regards,
Peter T
 
P

Peter T

Strange, I didn't see this when I followed up to your earlier message.
Anyway I trust the example in the follow-up clarifies.

Regards,
Peter T
 
C

Chip Pearson

ByVal - Creates a whole new replica of the object with all of the same
settings as the object that is being passed onto it,

Nope. Peter is correct that ByVal and ByRef specify only how the
pointer to the object is passed, not whether the object itself, or any
sort of clone of the object, is passed. It would tremendous overhead
to create a clone or replica of the object and then pass that. You
can pass the Application object around using ByVal and certainly no
replica of the Application is created. That would mean creating
replicas of all worksheets in all open workbook and all that. That's
just not how it works.

ByVal and ByRef work the same way regardless of whether the object is
defined in the typelib, such as a Range, or whether it is a custom
object like Class1. Internal to VBA's "compiled" code, I don't think
such as distinction is even made.

You can see this quite simply with the following code. First, create a
class name Class1 containing

Public Text As String

Then in a normal module, use

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim C1 As Class1
Dim C2 As Class1

Sub AAA()
Set C1 = New Class1
Set C2 = New Class1
C1.Text = "111"
C2.Text = "222"
Debug.Print "#1 Before PassByVal: C1: " & C1.Text & " C2: " & C2.Text
' pass ByVal
PassByVal C1
Debug.Print "#2 After PassByVal: C1: " & C1.Text & " C2: " & C2.Text

' reset
C1.Text = "111"
C2.Text = "222"
Debug.Print "#3 Before PassByRef: C1: " & C1.Text & " C2: " & C2.Text
' pass ByRef
PassByRef C1

Debug.Print "#4 After PassByRef: C1: " & C1.Text & " C2: " & C2.Text

End Sub

Sub PassByVal(ByVal C As Class1)
Set C = C2
End Sub

Sub PassByRef(ByRef C As Class1)
Set C = C2
End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

The immediate window will show:

#1 Before PassByVal: C1: 111 C2: 222
#2 After PassByVal: C1: 111 C2: 222
#3 Before PassByRef: C1: 111 C2: 222
#4 After PassByRef: C1: 222 C2: 222


Line #1 shows that C1 has text 111 and C2 has text 222, as one would
expect. After C1 is passed ByVal to PassByVal, line #3 shows that its
Text value is still 111. This indicates that the original object C1
was not changed to point to another object. *WITHIN PassByVal*, C is
changed to point to C2, but since the pointer was ByVal, the object is
unchanged back in Sub AAA.

Line #3 is just from the reset to original values. Line #4 shows that
BOTH C1 and C2 now have a Text value of 222, which is so because in
PassByRef, the parameter C (pointing to C1) is changed to point to C2.

Cordially,
Chip Pearson
Microsoft MVP
Excel Product Group
Pearson Software Consulting, LLC
www.cpearson.com
(email on web site)
 
R

Ronald R. Dodge, Jr.

But that still doesn't answer the question to how to deal with the original
issue with *NON* intrinsic objects.


As to answer your question, even within the VBA help file under
"Statements", then any one of the items listed below:

Function Statement
Property Get Statement
Property Let Statement
Property Set Statement
Sub Statement

When you look up the ByVal, it states:

---START QUOTE---

Optional. Indicates that the argument is passed by value.

---END QUOTE---

If you click on the link of "by value", it further states:

---START QUOTE---

by value

A way of passing the value of an argument to a procedure instead of passing
the address. This allows the procedure to access a copy of the variable.
As a result, the variable's actual value can't be changed by the procedure
to which it is passed.

---END QUOTE---

Also as stated in other sources such as in Access 2002 VBA Handbook by
Susann Novalis and Dana Jones on page 367:

---START QUOTE---

When you send a copy of the variable's value, VBA creates a copy in another
temporary storage location in memory and sends the copy. The called
procedure can use the copy and may even change it's values, but because the
called procedure is working with a copy, the variable itself is not
affected. Sending a copy of the variable is called passing the variable by
value.

---END QUOTE---

While this is VBA with Excel, regardless if it's within Excel or Access,
it's still the same VBA language and structure, just with a different set of
objects and variables pertinent to the application. Okay, here's one from
"Using Excel Visual Basic For Applications, Second Edition" by Jeff Webb on
page 52:

---START QUOTE---

Besides data types, you can also specify how Visual Basic passes arguments
to a procedure. By default, Visual Basic passes a reference to the
argument. Any changes to the argument are reflected in the variable that
was passed in as the argument.

---END QUOTE---

This is stating that, using ByRef, or the omission of using either one of
the 2 key words, any changes done to the object is reflected both inside and
outside of the called procedure.

Now, go to page 53 of the same book, but only for the ByVal keyword:

---START QUOTE---

When you pass arguments by value, Visual Basic makes a temporary copy of the
variable and passes the copy rather than the original. When the procedure
ends, Visual Basic throws the copy away--and changes are discarded.

---END QUOTE---

Though I'm dealing with passing arguments onto properties as opposed to
methods, it's the same deal. That very last one quoted pretty much spelt it
out.

You wanted my sources for my understanding of these 2 keywords, you got
them.


However, there's one exception to this rule, and you relied on this one
exception.

Variable/Object names that are global to the application, those names can't
be used for anything else, so when you attempt to use the ByVal with such
variables or variable types, they won't react the same way as described in
the help files. For that reason, these global variables/objects are known
as intrinsic to the application, which is why they have a different behavior
as compared to any other variable/object that's not considered as global at
the application level.
--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
R

Ronald R. Dodge, Jr.

See below for your code and with what you printed out.

C1 and C2 are module level object variables

First, when you pass C1 ByVal, when you have set C (the local variable to
the called procedure) to C2 (module level variable), note, it didn't change
C1 at all. That's cause C was just a replica of C1, not a reference to C1.
That is why your debug line of # 2 still shows as "111" for C1, not "222".

The second time, you pass C1 but only ByRef, and when you have C set to C2,
C1 was also changed to C2, thus why your debug line of # 4 shows as "222"
not only for C2, but also for C1. Therefore, your example just proved my
point.



'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim C1 As Class1
Dim C2 As Class1

Sub AAA()
Set C1 = New Class1
Set C2 = New Class1
C1.Text = "111"
C2.Text = "222"
Debug.Print "#1 Before PassByVal: C1: " & C1.Text & " C2: " & C2.Text
' pass ByVal
PassByVal C1
Debug.Print "#2 After PassByVal: C1: " & C1.Text & " C2: " & C2.Text

' reset
C1.Text = "111"
C2.Text = "222"
Debug.Print "#3 Before PassByRef: C1: " & C1.Text & " C2: " & C2.Text
' pass ByRef
PassByRef C1

Debug.Print "#4 After PassByRef: C1: " & C1.Text & " C2: " & C2.Text

End Sub

Sub PassByVal(ByVal C As Class1)
Set C = C2
End Sub

Sub PassByRef(ByRef C As Class1)
Set C = C2
End Sub

'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

--
Thanks,

Ronald R. Dodge, Jr.
Production Statistician
Master MOUS 2000
 
P

Peter T

There is nothing there that says anything about creating a replica object
for ByVal. The closest to what you have in mind is this -
When you pass arguments by value, Visual Basic makes a temporary copy of
the variable and passes the copy rather than the original. When the
procedure ends, Visual Basic throws the copy away--and changes are
discarded.

But you are missing the point. An object variable is a pointer to an object.
Sending an object ByVal makes a copy of the pointer as a new object
variable. Both original and new variables both point to the same object.
This bit - "and changes are discarded" as relates to an object ByVal refers
to any reassignment or destruction of the object variable, but not to any
changed properties of the actual object which will persist.

I take it you didn't try the example I gave that demonstrates.

Regards,
Peter T
 
R

Ronald Dodge

Take a look at Chip Pearson's response, then my response right back using
his own code example and debug print out.

Yes, an object variable is a pointer to an object, but that is the only way
how a programmer can interact with an object. However, there is a pretty
big difference difference between ByRef and ByVal, and you seem to be
missing it, just like Chip also missed it.

If ByVal and ByRef both merely had a pointer to the original object, C1 in
lines 2 and 4 would both be stating "222", not "111" in Line 2 and "222" in
Line 4. Study carefully the difference between Line # 2 and Line # 4 in his
example.
 
P

Peter T

You have misinterpreted Chip's example.

In the ByRef example the object variable C1 is in effect is re-assigned to
point to the C2 instance, at which moment the C1 class is destroyed as the
C1 pointer was the sole reference to the class object. That did not occur
with the ByVal example. Totally predictable and consistent with what I have
been trying to explain, without success.

Is there a reason you do not want to try the example I had posted, maybe if
you do things will become clearer.

Regards,
Peter T
 
R

Ronald Dodge

First off, in your example, what is the below doing to the object variable,
if anything:

objPtr(<argument1>)

This is too incomplete as there could be different things taking place.


Okay, in Chip's example, let's look at the ByVal route first.

The moment it was passed to C in the "PassByVal" sub, a replica was created.
The object pointed to C1 is still retained by the reference to C1.
Therefore, you now have 3 different objects created within VBA.

When C was set to C2 within the "PassByVal" sub, the object variable of C
pointed to the same object as the Object Variable of C2. However, the
object variable of C1 did not point to the same object, that the object
variable of C2 pointed to. At this same point of time, the replica that was
created via the ByVal keyword is now destroyed, so now you have C (local
variable) and C2 (module variable) both pointing to the same object while C1
(module variable) is pointed to the original instance it was created to
point to. Now, you are back to 2 different objects, which are the original
2 created in the procedure that called on the sub procedure.

Now, when the sub procedure ended, the object variable of C was also set to
NOTHING as it went out of scope.

Hence, when C1.Text was printed, it still printed "111" while C2.text was
printed as "222".


Now, C1.Text and C2.Text was put to "111" and "222" respectively.

Next line, but only passing to the sub procedure of "PassByRef"

Now, C1 is passed into the "PassByRef" subprocedure, but onlly as "ByRef",
thus the local object variable of C in "PassByRef" is referencing to the
exact same object as the module level object variable of C1. At this point,
you still have 2 objects within VBA and didn't create a third object like
you had in the "PassByVal" subprocedure.

Now, let's move onto the Set line within the sub procedure of "PassByRef"

At this point, the local object variable of C is set to point to the same
object as the module level object variable of C2 points to.
However, cause of the local object variable of C was reference to the same
object as the module level of C1 was referencing to, the object variable of
C1 was also pointed to the same object as the object variable of C2.
Therefore, as a result, the object that the object variable of C1 was
originally pointed to has been destroyed.

The problem I have with the PassByVal method, it replicates the object,
which I don't want that.

The problem I have with the PassByRef method, it not only redirects the
local variable (Desired), but also redirect the original object variable
that was passed onto the sub procedure to the object of what it has been
directed to point to indirectly via the local object variable (Undesired).
 
R

Ronald Dodge

In case I wasn't fully clear on what's undesired, I want any aspect of the
object change to be reflected (thus why the ByRef), but I don't want any
other object variable pointing to the same object as the one object variable
is prior to it being redirected, for those other to be redirected (hence the
undesired effect of the ByRef).

Such as in Chips example.

When C was redirected, I didn't want C1 to also be redirected, but that is
what takes place when using the ByRef keyword.
 
R

Ronald Dodge

Chip,

I modified the follwing code:

Sub PassByVal(ByVal C As Class1)
Set C = C2
End Sub

To:

Sub PassByVal(ByVal C As Class1)
C.Text = "333"
Debug.Print "#5 During PassByVal: C1: " & C1.Text & " C2: " & C2.Text &
" C: " & C.Text
End Sub

And I stand corrected about the replication bit. Note, you were using the
redirecting method as that was working out the same way as what I was saying
in the first place. That wasn't proving anything to me. I changed the code
to use the property of the object using the adjusting method, and that was
where the proof was at. However, note, there is nothing in the
documentation to state that it works like this for any *NON* intrinsic
objects.

#1 Before PassByVal: C1: 111 C2: 222
#5 During PassByVal: C1: 333 C2: 222 C: 333
#2 After PassByVal: C1: 333 C2: 222
#3 Before PassByRef: C1: 111 C2: 222
#4 After PassByRef: C1: 222 C2: 222
 
R

Ronald Dodge

Maybe, but it was more so the method that he used that worked out the same
under both scenarios, so that didn't prove anything to me. What did prove
it to me was when I changed the code inside of the ByVal method to change
the property of the local object and then print all 3 object variable's Text
property to the immediate window while still in the method and after
changing the Text property of the local object variable.

However, note, there is nothing in the documentations to make this point
dealing with *NON* intrinsic objects.

Does that mean the other sources are incorrect?

Some of them are (primarily the ones in the help files, though they are so
vague anyhow, they don't do much good), while others not really so as they
mainly used simple data type variables to make their point, and then used
intrinsic objects to show that it doesn't work that way for those objects.
However, there's nothing stated anywhere that I saw dealing with custom
objects.

Maybe lesson learned by all 3 of us. For me, it was realizing that the
documentations were misleading or incorrect (which ever may have been the
case) while for the 2 of you, using a proper example that shows a clear
distinction without using something that could end up returning the same
result (In Chip's case) and don't use an example that has something in it
that has an unknown in it like what your example had with the ObjPtr
Function.
 
P

Peter T

I thought Chip's example was clear but I guess we all see things different
ways.

In my example use of ObjPtr was to demonstrate that all the references, the
original, the ByRef & ByVal all pointed to the same intrinsic object and
therefore no copy of the object was created. I posted this early in the
thread when you were disinclined to accept that was how it worked.

The example also showed, that as far as the intrinsic object is concerned,
there is no difference between using ByVal or ByRef. However there most
certainly is a difference in what may or may not happen to the object
variables depending on how they are passed.

I fully accept there are always better ways of explaining things, but that
does not mean either of the examples were not valid.

However, note, there is nothing in the documentations to make this point
dealing with *NON* intrinsic objects.

I don't follow "non". I used the term "intrinsic" to refer to the object
"thing", as distinct from an object variable which stores the address that
"points" to the block of memory where the object currently exists
(simplified).
Does that mean the other sources are incorrect?

I haven't read anything that is technically incorrect but I agree there is
not much in the way of explanation on this particular aspect.


I still don't follow your objective and problem. Why not post a simplified
example.

Regards,
Peter T
 
C

Chip Pearson

Let's try looking at the results provided by ObjPtr and the Is
operator.

Create Class1 with

Public Text As String


Then, in a regular module, use

'''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''
Dim C1 As Class1
Dim C2 As Class1

Sub AAA()
Debug.Print "-----------------------------------"
Set C1 = New Class1
Set C2 = New Class1

N -1
Debug.Print N, "C1", ObjPtr(C1)
Debug.Print N, "C2", ObjPtr(C2)

Debug.Print "Before PassByVal"
C1.Text = "1"
C2.Text = "2"
PassByVal C1
Debug.Print "After PassByVal"
Debug.Print N, "C1", ObjPtr(C1), C1.Text, C2.Text
Debug.Print N, "C2", ObjPtr(C2), C2.Text, C2.Text
Debug.Print N, "Is", C1 Is C2

Debug.Print "Before PassByRef"
C1.Text = "1"
C2.Text = "2"
PassByRef C1
Debug.Print "After PassByRef"
Debug.Print N, "C1", ObjPtr(C1), C1.Text, C2.Text
Debug.Print N, "C2", ObjPtr(C2), C1.Text, C2.Text
Debug.Print N, "Is", C1 Is C2
Debug.Print "-----------------------------------"
End Sub


Sub PassByVal(ByVal C As Class1)
Set C = C2
C.Text = "byval"
End Sub

Sub PassByRef(ByRef C As Class1)
Set C = C2
C.Text = "byref"
End Sub

Function N(Optional L As Long) As Long
Static M As Long
If L <> 0 Then M = 0: Exit Function
M = M + 1: N = M
End Function
'''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''



This prints in the Immediate window the following:

-----------------------------------
1 C1 221395496
2 C2 221395736
Before PassByVal
After PassByVal
3 C1 221395496 1 byval
4 C2 221395736 byval byval
5 Is False
Before PassByRef
After PassByRef
6 C1 221395736 byref byref
7 C2 221395736 byref byref
8 Is True
-----------------------------------


Look at the following. The addresses provided by ObjPtr in lines 1 and
2 are the base address, nothing notable here. Lines 3 and 4 show that
after being passed to PassByVal, C1 and C2 are still separate, even
though PassByVal set the point C (which points to C1) to C2. Because
the pointer itself is a temporary pointer on stack not the heap, C1
isn't change to C2 *outside* PassByVal. *WITHIN* PassByVal, C points
to C2 but this change doesn't propagate back up to AAA.

Line 5 compares the object C1 and C2 and returns False since that are
not the same object.

Lines 6 and 7 show that in PassByRef, the pointer C (which is the same
as C1) is indeed changed to the address of C2 and that this change
propagates back into AAA. The Is operator in line 8 shows that the
changed pointer C is changed from C1 to C2. This is possible only in a
ByRef pass, not a ByVal pass.

In both ByVal and ByRef passes, the members of the object can be
changed. This is shown by the changes to the Text property of Class1.
There is no way to pass an object and prevent the called proc from
changing members of the object. ByVal only prevents the called proc
from change which object (in the caller) is pointed to by the
reference. If a "replica" of an object were passed to a ByVal proc,
then the member and private variables of the replicate would be
changed by the called proc, and the member and private variables of
the object from which the "replica" is created would not be changed.
But this is not the case, as the code below shows:

Sub BBB()
Dim CC As Class1
Set CC = New Class1
CC.Text = "bbb"
Debug.Print "Before PassByVal2:", CC.Text
PassByVal2 CC
Debug.Print "After PassByVal2:", CC.Text
End Sub
Sub PassByVal2(ByVal C As Class1)
C.Text = "byval"
End Sub


This sends to the Immediate window the following:

Before PassByVal2: bbb
After PassByVal2: byval

Even though CC was passed ByVal, its member can be changed by the
called proc. This would not be the case if some sort of "replica" or
clone was passed.

Cordially,
Chip Pearson
Microsoft MVP
Excel Product Group
Pearson Software Consulting, LLC
www.cpearson.com
(email on web site)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top