ByVal vs. ByRef

  • Thread starter Thread starter eBob.com
  • Start date Start date
E

eBob.com

I guess I have fallen into the habit of generally passing values ByRef,
unless I am certain that the method will never need to alter the value.
Also I have never been quite sure what it means to pass a reference type
ByVal. And I have never had a problem. Until this past weekend.

This past weekend I was playing around with a TreeView representation of a
directory and developed the code attached below. The code is very
straightforward. A button click routine establishes the topmost node and
then calls a recursive subroutine, Populate, to populate the rest of the
tree. The second argument to Populate is a TreeNode and I had that being
passed ByRef, because a TreeNode is a reference type, and, as I said, I am
not sure what it means to pass a reference type ByVal. And besides it has
always worked for me to pass practically everything ByRef.

The result was that the topmost node was duplicated!

I had no clue what was going on, and so, after a lot of head scratching and
searches, I posted a message here which for some reason never appeared. But
a subsequent update to my original note did appear in at least the easynews
copy of this ng but not here (here being the microsoft maintained copy of
the ng). And Captain Jack kindly informed me that the problem was that I
was passing the TreeNode parameter ByRef and he is certainly right.

BUT ... it seems to me that the parameter should be passed ByRef. The
Populate function is going to be hanging subnodes off the passed topmost
node. And my basic understanding of ByRef is that that is the way to pass
something which the subroutine/function might change.

So, please, why does the second argument to my Populate function have to be
passed ByVal?

Also, since my searches have only confirmed that I am not the only person
confused by this, what does it mean to pass a reference type ByVal? Some
Internet posts say that the only difference is that you are passing a copy
of the pointer rather than the pointer itself. But I tried this dirt simple
little experiment ...

Public Class Form1
Dim astring As String = "abc"
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
touchitbyval(astring)
MsgBox(astring)
End Sub
Sub touchitbyval(ByVal somestring As String)
somestring = "xyz"
End Sub
End Class

.... and the value of astring is not changed! That makes me think that maybe
a clone, of the object, not the reference, is being passed, which would be
consistent with what it means to pass a value type ByVal, but is not what
many Internet posts say. And if a clone is being passed in my TreeView
program, the one attached below, then I do not see how my Populate function
is able to hang additional nodes off it.

If one of you gurus here who REALLY understand this stuff could provide an
authoritative explanation I would appreciate it, and you would be making a
real contribution to the community as I am certainly not the only one who is
confused.

Thanks, Bob


Option Strict On
Option Explicit On

Imports System.IO

Public Class Form1

Enum PopRtnCode
OK 'OK
uae 'UnauthorizedAccessException
miscerror 'some error other than above
End Enum

Private Sub btnBuildTree_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles btnBuildTree.Click
If Not Directory.Exists(tbxTopDir.Text) Then
Else
Dim basenode As New TreeNode(tbxTopDir.Text)
tvDir.Nodes.Add(basenode)
Dim tag As String
Select Case Populate(tbxTopDir.Text, tvDir.Nodes(0))
Case PopRtnCode.OK
tag = ""
Case PopRtnCode.uae
tag = " <Access Exception>"
Case PopRtnCode.miscerror
tag = " <Misc. Error>"
End Select
tvDir.Nodes(0).Text = tbxTopDir.Text & tag
End If
End Sub

Function Populate(ByRef directoryValue As String, ByVal parentNode As
TreeNode) _
As PopRtnCode 'pass 2nd arg ByRef to demonstrate problem

tbxDebug.AppendText("called for directory: """ & directoryValue & _
""" / parentnode: " & parentNode.ToString &
vbCrLf)
'depth += 1

Dim directoryArray() As String

Try
directoryArray = Directory.GetDirectories(directoryValue)
Catch ex As UnauthorizedAccessException
Return PopRtnCode.uae
Catch ex As Exception
Return PopRtnCode.miscerror
End Try

If (directoryArray.Length <> 0) Then

For Each directory As String In directoryArray
Dim substringDirectory As String
substringDirectory = directory.Substring( _
directory.LastIndexOf("\") + 1, _
directory.Length - directory.LastIndexOf("\") - 1)

tbxDebug.AppendText("substringDirectory is: " &
substringDirectory & vbCrLf)

Dim myNode As TreeNode = New TreeNode(substringDirectory)

parentNode.Nodes.Add(myNode)
myNode.Expand()
Application.DoEvents()
'MsgBox("pace")

Dim tag As String = ""

Select Case Populate(directory, myNode)
Case PopRtnCode.OK
tag = ""
Case PopRtnCode.uae
tag = " <Access Exception>"
Case PopRtnCode.miscerror
tag = " <Misc. Error>"
End Select
myNode.Text = substringDirectory & tag

Next
End If

tbxDebug.AppendText("returning from handling directory: " &
directoryValue & vbCrLf)
'MsgBox("returning from handling directory: " & directoryValue)

End Function ' Populate


End Class
 
ByRef is used to pass a reference to the original variable, allowing
the called code to modify its contents.
ByVal is used to pass the value of the variable into the called code.

Here is a link to an MSDN article that explain it
http://msdn.microsoft.com/en-us/library/ddck1z30(VS.71).aspx

IMO, I try to avoid ByRef because it can be confusing when a local
variable in one method is modifed by code in another method, like
below

Public Class Form1

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles MyBase.Load
Dim astring As String = "abc"
touchitbyref(astring)
MsgBox(astring) 'this would now show xyz
End Sub
Sub touchitbyval(ByRef somestring As String)
somestring = "xyz"
End Sub
End Class
 
eBob.com said:
I guess I have fallen into the habit of generally passing values ByRef,
unless I am certain that the method will never need to alter the value.

"the Value" - is the important part of that statement.
Q: What is "the Value" of a reference variable?
A: A "pointer" to the object.
Also I have never been quite sure what it means to pass a reference type
ByVal.

When you pass reference objects around, you're passing a "pointer" to
that object, not the object itself. (NB: I'm putting the "P" word in
quotes here because, strictly speaking, it's a .Net Object Reference,
which is underpinned (somewhere) by a real (memory) Pointer; the
difference between /these/ two is fundamental to how Garbage Collection
works, but that's another story).

So, if you pass an object ByVal, you're passing that "pointer" ByVal, so
you can access (and /change/ properties on) the object in question but
you /can't/ create a new instance of that object and "return" it.
For that, you have to pass the object ByRef.

And my basic understanding of ByRef is that that is the way to pass
something which the subroutine/function might change.

The definition of "change" is the problem here.

It's not so much "alter the parameter I'm passed"

but more like "create a new [instance] value for this parameter that my
Caller needs to know about".

So, passing a TreeNode ByVal you can add as many child Nodes to it as
you like; it's still the /same/ TreeNode that was passed in.
Also, since my searches have only confirmed that I am not the only person
confused by this, what does it mean to pass a reference type ByVal? Some
Internet posts say that the only difference is that you are passing a copy
of the pointer rather than the pointer itself. But I tried this dirt simple
little experiment ...

<snip string-sample-code />
Ah.

Strings are Immutable, which in the current context is to say,
(a) they're a bit odd, and
(b) once you've put a value in one, you can't change that value without
implicitly creating a whole new String object.

If it's any consolation, any /other/ reference type /would/ have worked
as you expect.


With regards your supplied code, I would make a couple of suggestions:

(1) Have a look at System.IO.Path; it has a bunch of really useful Path
manipulation functions, like GetFileName(path); saves all that mucking
about with Substring().

(2) I would suggest only expanding a single directory at a time; if
you're working a big tree, it can take a long time to load lots (and
lots) of Nodes that your user may never look at; just give them the ones
they ask for (a "dummy" TreeNode class and the TreeView's BeforeExpand
event are your friends here).
If you are going to load the whole tree in one go, think carefully about
expanding it as you go along; again, with a large tree, this can get
quite slow.

HTH,
Phill W.
 
To simplify:

When you pass a reference type (any class instance) byval:
A copy of the passed *pointer* to the reference is made.
When you pass a reference type byref:
A reference to the passed pointer is made.
When you pass a value type (double, date, boolean, integers, decimals, etc.)
byval:
A copy of the passed *value* is made.
When yo upass a value type byref:
A reference to the passed value is made.

With reference types, it is generally more efficient to pass them byval,
which is the default in .NET.
With value types, it is generally safer to pass them byval, again the
default.

-Scott


Phill W. said:
eBob.com said:
I guess I have fallen into the habit of generally passing values ByRef,
unless I am certain that the method will never need to alter the value.

"the Value" - is the important part of that statement.
Q: What is "the Value" of a reference variable?
A: A "pointer" to the object.
Also I have never been quite sure what it means to pass a reference type
ByVal.

When you pass reference objects around, you're passing a "pointer" to that
object, not the object itself. (NB: I'm putting the "P" word in quotes
here because, strictly speaking, it's a .Net Object Reference, which is
underpinned (somewhere) by a real (memory) Pointer; the difference between
/these/ two is fundamental to how Garbage Collection works, but that's
another story).

So, if you pass an object ByVal, you're passing that "pointer" ByVal, so
you can access (and /change/ properties on) the object in question but you
/can't/ create a new instance of that object and "return" it.
For that, you have to pass the object ByRef.

And my basic understanding of ByRef is that that is the way to pass
something which the subroutine/function might change.

The definition of "change" is the problem here.

It's not so much "alter the parameter I'm passed"

but more like "create a new [instance] value for this parameter that my
Caller needs to know about".

So, passing a TreeNode ByVal you can add as many child Nodes to it as you
like; it's still the /same/ TreeNode that was passed in.
Also, since my searches have only confirmed that I am not the only person
confused by this, what does it mean to pass a reference type ByVal? Some
Internet posts say that the only difference is that you are passing a
copy
of the pointer rather than the pointer itself. But I tried this dirt
simple
little experiment ...

<snip string-sample-code />
Ah.

Strings are Immutable, which in the current context is to say,
(a) they're a bit odd, and
(b) once you've put a value in one, you can't change that value without
implicitly creating a whole new String object.

If it's any consolation, any /other/ reference type /would/ have worked as
you expect.


With regards your supplied code, I would make a couple of suggestions:

(1) Have a look at System.IO.Path; it has a bunch of really useful Path
manipulation functions, like GetFileName(path); saves all that mucking
about with Substring().

(2) I would suggest only expanding a single directory at a time; if you're
working a big tree, it can take a long time to load lots (and lots) of
Nodes that your user may never look at; just give them the ones they ask
for (a "dummy" TreeNode class and the TreeView's BeforeExpand event are
your friends here).
If you are going to load the whole tree in one go, think carefully about
expanding it as you go along; again, with a large tree, this can get quite
slow.

HTH,
Phill W.
 
Thank you Phill. You reply was very helpful. When I tried to run the code
against my (entire) C drive, not knowing any better (i.e. before seeing your
reply), I discovered the wisdom of expanding a node only upon user request.

But I still don't understand why I have to pass the TreeNode argument ByVal.
I understand (now) why passing it ByVal works. What I don't understand is
why passing it ByRef doesn't work.

Thanks again, Bob


Phill W. said:
eBob.com said:
I guess I have fallen into the habit of generally passing values ByRef,
unless I am certain that the method will never need to alter the value.

"the Value" - is the important part of that statement.
Q: What is "the Value" of a reference variable?
A: A "pointer" to the object.
Also I have never been quite sure what it means to pass a reference type
ByVal.

When you pass reference objects around, you're passing a "pointer" to that
object, not the object itself. (NB: I'm putting the "P" word in quotes
here because, strictly speaking, it's a .Net Object Reference, which is
underpinned (somewhere) by a real (memory) Pointer; the difference between
/these/ two is fundamental to how Garbage Collection works, but that's
another story).

So, if you pass an object ByVal, you're passing that "pointer" ByVal, so
you can access (and /change/ properties on) the object in question but you
/can't/ create a new instance of that object and "return" it.
For that, you have to pass the object ByRef.

And my basic understanding of ByRef is that that is the way to pass
something which the subroutine/function might change.

The definition of "change" is the problem here.

It's not so much "alter the parameter I'm passed"

but more like "create a new [instance] value for this parameter that my
Caller needs to know about".

So, passing a TreeNode ByVal you can add as many child Nodes to it as you
like; it's still the /same/ TreeNode that was passed in.
Also, since my searches have only confirmed that I am not the only person
confused by this, what does it mean to pass a reference type ByVal? Some
Internet posts say that the only difference is that you are passing a
copy
of the pointer rather than the pointer itself. But I tried this dirt
simple
little experiment ...

<snip string-sample-code />
Ah.

Strings are Immutable, which in the current context is to say,
(a) they're a bit odd, and
(b) once you've put a value in one, you can't change that value without
implicitly creating a whole new String object.

If it's any consolation, any /other/ reference type /would/ have worked as
you expect.


With regards your supplied code, I would make a couple of suggestions:

(1) Have a look at System.IO.Path; it has a bunch of really useful Path
manipulation functions, like GetFileName(path); saves all that mucking
about with Substring().

(2) I would suggest only expanding a single directory at a time; if you're
working a big tree, it can take a long time to load lots (and lots) of
Nodes that your user may never look at; just give them the ones they ask
for (a "dummy" TreeNode class and the TreeView's BeforeExpand event are
your friends here).
If you are going to load the whole tree in one go, think carefully about
expanding it as you go along; again, with a large tree, this can get quite
slow.

HTH,
Phill W.
 
eBob.com said:
But I still don't understand why I have to pass the TreeNode argument ByVal.
I understand (now) why passing it ByVal works. What I don't understand is
why passing it ByRef doesn't work.

This is interesting. And tricky. You are passing a property ByRef to Function populate. This urtges
VB to create the code to write the value returned from the function parameter back to the
property after the function returned.

Use BYVAL again and try this (from btnBuildTree_Click)

Dim node = tvDir.Nodes(0)
Dim result = Populate(tbxTopDir.Text, node)
tvDir.Nodes(0) = node
Select Case result
'...

Now it behaves like the ByRef version. (however, 'node' could not have been changed
inside Function Populate but that doesn't matter in this case)

The assignment is the key. Obviously this is how the Treeview works if you assign
a Treenode at a certain position. So, you are seeing the effect of a combination
of passing a property ByRef and how the Treeview works.
 
Thank you Armin. You've certainly advanced my understanding of what it
happening. But one thing I still don't understand is why

TreeView.Nodes(0) = TreeNode

ADDs a node. The statement looks to me like it should replace the 0th node.
Sample code which I have found for adding a node uses

TreeView.Nodes.Add or TreeNode.Nodes.Add

Thanks again, Bob
 
eBob.com said:
But I still don't understand why I have to pass the TreeNode argument ByVal.
I understand (now) why passing it ByVal works. What I don't understand is
why passing it ByRef doesn't work.

I think that's because you still don't understand the difference
between ByVal and ByRef.


Here's a simple rule of thumb: you should use ByRef when you have a
FUNCTION and desire to return more than one value. The various
TryParse functions are perfect examples of correct ByRef usage.

Now, to understand the difference between ByRef and ByVal you have to
pay attention to the difference between a parameter and an argument.

Function F (ByRef Parameter as Integer) As Boolean
Parameter = 1

Return False
End Function

Sub Demo()
Dim Argument as Integer

Dim result as Boolean

Result = f(Argument)

MsgBox("Result was: " + Result.ToString + vbCRLF + _
"Argument is now: " + Argument.ToString)

End Sub

When you want to change the variable Argument, use ByRef, when you only
want to allow the Function/Sub to access the CONTENTS of the variable
(whether that's a plain integer or a class with a thousand properties),
you'd use ByVal.

To illustrate this in a slightly different way, I'll give an example of
the OTHER case of when you'd want to use ByRef other than returning
multiple values.

Sub HelloToStream (ByRef st as StreamWriter)
If st Is Nothing Then
st = New StreamWriter("Demo.txt")
End If

st.WriteLn("Hello, World")

End Sub

Sub ByeToStream (ByRef st as StreamWriter)
If st Is Nothing Then
st = New StreamWriter("Demo.txt")
End If

st.WriteLn("Bye, Bye")

End Sub


Sub OtherDemo()
Dim argument as StreamWriter = Nothing

HelloToStream(argument)
ByeToStream(argument)

End Sub

You can switch the order of HelloToStream and ByeToStream and they will
both write to the same stream.

Now the explanation: the first example is not specific to reference or
value types, it doesn't matter, if you want to return more than one
"value" ByRef is how you do it. This second example is specific to
Reference types: when you have a reference type that may be
initialized/created in various methods, it has to be ByRef.

This second example is less common because generally you can do the
same thing with a class level property or field. But it's not unheard
of (for instance when your methods are in different classes).



Hope this helps.
 
eBob.com said:
Thank you Armin. You've certainly advanced my understanding of what it
happening. But one thing I still don't understand is why

TreeView.Nodes(0) = TreeNode

ADDs a node. The statement looks to me like it should replace the 0th node.
Sample code which I have found for adding a node uses

TreeView.Nodes.Add or TreeNode.Nodes.Add

I don't know it, too. I've tried

TreeView1.Nodes(0) = TreeView1.Nodes(0)

and it also adds a node (as expected now). Well, maybe you find something about
it in the docs.
 
Back
Top