Tom’s Blog

August 17, 2009

ByVal vs ByRef – What is the Difference?

Filed under: General,Visual Basic — Tom Shelton @ 10:44 pm

Over the years, I’ve noticed that there seems to be a lot of confusion about the difference between passing a parameter ByVal vs. ByRef.    So, I thought I’d spend some time writing something on this topic.  It should be noted, that while I will be discussing this from the view point of VB.NET, the same basic concepts also apply to VB.CLASSIC – it’s just that some of the terminology is different.

First, let’s cover the definition of these two keywords as they are presented in the Visual Basic documentation.

ByVal

Specifies that an argument is passed in such a way that the called procedure or property cannot change the value of a variable underlying the argument in the calling code.

ByRef

Specifies that an argument is passed in such a way that the called procedure can change the value of a variable underlying the argument in the calling code.

On the surface, these two statements seem simple enough.  ByVal (short for By Value) means that the called procedure can not alter the value that was passed.  In essence, when you pass ByVal – a copy of the original value is made and passed to the procedure.  ByRef (short for By Reference) on the other hand is just the opposite.  When, a value is passed ByRef, then a reference – aka pointer (in fact it is more accurate to say that it is a C++ reference then a pointer) – to the original value is passed, so any changes made to this value alter the callers state.  Just to test this out, lets create a little test code and see what happens:

 

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:     End Sub
  22:  
  23:     Sub PassIntByVal(ByVal i As Integer)
  24:         i = 5
  25:     End Sub
  26:  
  27:     Sub PassIntByRef(ByRef i As Integer)
  28:         i = 5
  29:     End Sub
  30:  
  31: End Module

And our output:

output001

So far so good.  It looks exactly like we should expect given the definitions of ByVal and ByRef.  So, if things work according to the definition, why the confusion?   Well, lets expand this simple example a bit and see if things continue to hold up – this time by creating a structure and putting it to the same test as above:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:         Console.WriteLine()
  22:  
  23:         Dim ts As TestStructure
  24:  
  25:         ' Pass a structure ByVal
  26:         Console.WriteLine("Before ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  27:         PassStructureByVal(ts)
  28:         Console.WriteLine("After ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  29:  
  30:         Console.WriteLine()
  31:  
  32:         ' Pass a strucutre ByRef
  33:         Console.WriteLine("Before ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  34:         PassStructureByRef(ts)
  35:         Console.WriteLine("After ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  36:  
  37:     End Sub
  38:  
  39:     Sub PassIntByVal(ByVal i As Integer)
  40:         i = 5
  41:     End Sub
  42:  
  43:     Sub PassIntByRef(ByRef i As Integer)
  44:         i = 5
  45:     End Sub
  46:  
  47:     Sub PassStructureByVal(ByVal s As TestStructure)
  48:         s.i = 5
  49:         s.j = 10
  50:     End Sub
  51:  
  52:     Sub PassStructureByRef(ByRef s As TestStructure)
  53:         s.i = 5
  54:         s.j = 10
  55:     End Sub
  56:  
  57:     Structure TestStructure
  58:         Public i As Integer
  59:         Public j As Integer
  60:     End Structure
  61: End Module

And our output:

output002

The output should be consistent with our expectations.  Again, what’s the problem?  I’m going to do one more expansion of our code – lets add a custom class into the mix:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim i As Integer = 0
   8:  
   9:         ' Pass an integer ByVal
  10:         Console.WriteLine("Before ByVal Call: i = {0}", i)
  11:         PassIntByVal(i)
  12:         Console.WriteLine("After ByVal Call: i = {0}", i)
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' Pass an integer ByRef
  17:         Console.WriteLine("Before ByRef Call: i = {0}", i)
  18:         PassIntByRef(i)
  19:         Console.WriteLine("After ByRef Call: i = {0}", i)
  20:  
  21:         Console.WriteLine()
  22:  
  23:         Dim ts As TestStructure
  24:  
  25:         ' Pass a structure ByVal
  26:         Console.WriteLine("Before ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  27:         PassStructureByVal(ts)
  28:         Console.WriteLine("After ByVal Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  29:  
  30:         Console.WriteLine()
  31:  
  32:         ' Pass a strucutre ByRef
  33:         Console.WriteLine("Before ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  34:         PassStructureByRef(ts)
  35:         Console.WriteLine("After ByRef Call: ts.i = {0}, ts.j={1}", ts.i, ts.j)
  36:  
  37:         Console.WriteLine()
  38:  
  39:         Dim tc As New TestClass
  40:  
  41:         ' Pass a class ByVal
  42:         Console.WriteLine("Before ByVal Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  43:         PassClassByVal(tc)
  44:         Console.WriteLine("After ByVal Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  45:  
  46:         Console.WriteLine()
  47:  
  48:         ' Pass a class ByRef
  49:         Console.WriteLine("Before ByRef Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  50:         PassClassByRef(tc)
  51:         Console.WriteLine("After ByRef Call: tc.i = {0}, tc.j={1}", tc.i, tc.j)
  52:  
  53:     End Sub
  54:  
  55:     Sub PassIntByVal(ByVal i As Integer)
  56:         i = 5
  57:     End Sub
  58:  
  59:     Sub PassIntByRef(ByRef i As Integer)
  60:         i = 5
  61:     End Sub
  62:  
  63:     Sub PassStructureByVal(ByVal s As TestStructure)
  64:         s.i = 5
  65:         s.j = 10
  66:     End Sub
  67:  
  68:     Sub PassStructureByRef(ByRef s As TestStructure)
  69:         s.i = 5
  70:         s.j = 10
  71:     End Sub
  72:  
  73:     Sub PassClassByVal(ByVal c As TestClass)
  74:         c.i = 5
  75:         c.j = 10
  76:     End Sub
  77:  
  78:     Sub PassClassByRef(ByRef c As TestClass)
  79:         c.i = 6
  80:         c.j = 11
  81:     End Sub
  82:  
  83:     Structure TestStructure
  84:         Public i As Integer
  85:         Public j As Integer
  86:     End Structure
  87:  
  88:     Class TestClass
  89:         Public i As Integer
  90:         Public j As Integer
  91:     End Class
  92:  
  93: End Module

And the output:

output003

Take a look at the lines of output for the tc variable.  Have we been mislead by the documentation?   Because, on the surface at least, it sure appears as if tc was modified when passed ByVal.   The answer to this question is no – ByVal still worked exactly as it was supposed to. 

To understand why these results are in fact correct, requires a bit of understanding of the .NET type system.  The .NET CTS – or Common Type System – defines two basic type categories (each of which is further subdivided).  These categories are Value types and Reference types.  Here is what the documentation has to say regarding these two categories:

    • Value types

      Value types directly contain their data, and instances of value types are either allocated on the stack or allocated inline in a structure. Value types can be built-in (implemented by the runtime), user-defined, or enumerations. For a list of built-in value types, see the .NET Framework Class Library.

    • Reference types

      Reference types store a reference to the value’s memory address, and are allocated on the heap. Reference types can be self-describing types, pointer types, or interface types. The type of a reference type can be determined from values of self-describing types. Self-describing types are further split into arrays and class types. The class types are user-defined classes, boxed value types, and delegates.

Summary:  A value type variable directly contains it’s data, a reference type variable does not.   In practical terms, this means that the behavior demonstrated by the example code is in fact completely consistent with the documented behavior of ByVal and ByRef.  When passing ByVal, the copy that is made is the contents of the variable – in the case of a reference type, the value that the variable contains is not the instance, but the memory address of the instance on the heap.  Maybe a small diagram will help clarify:

heap

What this means in practical terms is that, if you pass an object ByVal – the objects properties can be modified, but the reference can not:

   1: Option Explicit On
   2: Option Strict On
   3:  
   4: Module Module1
   5:  
   6:     Sub Main()
   7:         Dim tc As TestClass = Nothing
   8:  
   9:         ' pass by value
  10:         Console.WriteLine("Passing tc ByVal")
  11:         CreateTCByVal(tc)
  12:         Console.WriteLine(If(tc IsNot Nothing, String.Format("tc.i = {0}, tc.j={1}", tc.i, tc.j), "tc is Nothing"))
  13:  
  14:         Console.WriteLine()
  15:  
  16:         ' pass by reference
  17:         Console.WriteLine("Passing tc ByRef")
  18:         CreateTCByRef(tc)
  19:         Console.WriteLine(If(tc IsNot Nothing, String.Format("tc.i = {0}, tc.j={1}", tc.i, tc.j), "tc is Nothing"))
  20:     End Sub
  21:  
  22:     Sub CreateTCByVal(ByVal c As TestClass)
  23:         c = New TestClass()
  24:         c.i = 5
  25:         c.j = 6
  26:     End Sub
  27:  
  28:     Sub CreateTCByRef(ByRef c As TestClass)
  29:         c = New TestClass()
  30:         c.i = 5
  31:         c.j = 6
  32:     End Sub
  33:  
  34:     Class TestClass
  35:         Public i As Integer
  36:         Public j As Integer
  37:     End Class
  38:  
  39: End Module

And the output:

output004

I hope this brief explanation of ByVal and ByRef can help clear up some of the confusion that seems to exist regarding these to argument passing methods.

1 Comment »

  1. Thanks a lot, very helpful !

    Comment by Etienne — May 27, 2013 @ 2:38 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress