IDeploy

adventures of a geek without a parachute

  Home  |   Contact  |   Syndication    |   Login
  35 Posts | 1 Stories | 41 Comments | 207 Trackbacks

News

Archives

Post Categories

Image Galleries

________

Articles

Blogroll

Humor

Who Drank My Beer?

 

    I was attending a .Net user group event recently, and chatting with some group members. One of the attendees remarked, “The thing I really I hate about VB is that ByVal doesn’t pass my objects by value, the compiler should catch that and make you use ByRef. It’s really misleading.” I am paraphrasing the statement, so I hope the person who made it won’t call me on the details.

 

 I immediately thought “Huh? ByVal does pass objects by value, but it’s the reference that’s passed that way, not the object itself!” If you had the same reaction, you may not want to read on, but if not this article may clarify some things for you. While the concepts are not particularly advanced, you’d be amazed by the number of developers (and I’m not talking beginners here) I’ve met that are unaware of what happens when passing different types ByVal.

 

The ByVal Key Word

Let’s start with a short review of the ByVal key word. It’s been around since VB 3 and maybe even earlier so we’re all familiar with it, right? According to the .NET framework documentation:

 

“The ByVal keyword indicates 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”

 

Many developers interpret this to mean that any variable passed to a procedure ByVal can’t be changed by the procedure. This is a correct interpretation, but in order to “own” the concept, one must consider the difference between the two variable types supported by .NET

 

Value Types Vs. Reference Types

A fundamental distinction in the Common Type System is the “value type” vs. the “reference type” variable. Managed code can allocate memory on the heap or on the stack. When an instance of a value type is created, it is stored directly on the stack. Memory for a value type is allocated when it is declared, and freed when the variable is no longer accessible.  In other words, an instance of a value type’s lifetime is determined by the lifetime of the variable that created it. Reference types are allocated and released differently, as we’ll see later.

 

If we declare an integer and set it to the value 21, the value is stored on the stack. The diagram below represents the result of the statement Dim intLegalDrinkingAge as integer   = 21. For the sake of simplicity, we’ll refer to the place on the stack that the memory is allocated as location “A”

 

Application Memory

Stack

Heap

A- intLegalDrinkingAge [21]

 

Figure 1 (memory allocation for a value type)

 

When intLegalDrinkingAge (a value type) is passed to a procedure by reference, the value is accessed directly. The called procedure may modify the intLegalDrinkingAge variable. While this is something most of us would have liked to do as teenagers, as programmers we may not want this to happen. We want to other procedures to promise not to change intLegalDrinkingAge, so we declare the parameter that accepts intLegalDrinkingAge ByVal.

 

                 Public Function CanPersonPurchaseBeer(ByVal LegalDrinkingAge _

                                     As Integer, ByVal BirthDate As DateTime) As Boolean

 

The ByVal key word tells the compiler to create a copy of the variable on the stack and pass the copy to the procedure. The figure below illustrates this. When the program runs, the value in location A is copied to location B, and location B is passed to CanPersonPurchaseBeer(). When CanPersonPurchaseBeer returns, the memory at location B is freed and the memory at location A remains in tact.

 

Application Memory

Stack

Heap

A- intLegalDrinkingAge [21]

 

B- intLegalDrinkingAge [21]

 

Figure 2 (memory allocation for a value type passed ByVal)

 

Reference types allocate memory on the both the heap and the stack. When an instance of a reference type is created, the instance is stored on the heap, and a reference to the instance is stored on the stack. Memory on the heap is managed by the CLR’s garbage collector. The lifetime of an instance of a reference type is determined by the variable that created it and by the CLR. Let’s take a look at the memory allocation that occurs for reference types. Imagine that we have a class called “Beer”, and the following code.

 

         Dim myBeer As New Beer

        Dim yourBeer As Beer = myBeer

 

Figure 3 below shows the memory allocation for these statements. For the first statement, an instance of the Beer class is created and placed on the heap. We’ll refer to the location on the heap as “Ha”. The myBeer variable is placed on the stack, and valued with a pointer to the location of the Beer Object. When the second line is executed, the yourBeer variable is placed on the stack (location Sb), and valued with a pointer to the Beer Object created in the first line. So myBeer and yourBeer point to the same object. You probably knew this already, and you may not be keen on the idea of sharing a beer with me anyway, but this concept is important to understanding the rest of the story.

 

Application Memory

Stack

Heap

Sa- myBeer [Ha]

Ha [Beer Object]

Sb- yourBeer[Ha]

 

Figure 3 (memory allocation for a reference type)

 

Now let’s say that I pass myBeer to you by value.

   

    Public Sub PassBeerByVal()

        HoldBeer(myBeer)

 

        If myBeer.PercentFull = 0 Then

            MessageBox.Show("You Drank My Beer!")

        End If

    End Sub

 

    Public Sub HoldBeer(ByVal passedBeer As Beer)

        ‘chug a lug

        passedBeer.DrinkAll()

    End Sub

 

Before HoldBeer is called, the stack variable for myBeer is copied (see fig 4). The copy is then passed to HoldBeer(). Did you catch that? The beer object (Ha) was not copied, the reference to the beer object (Sa) was copied! The copied reference (Sb) points to the same object as the original (Sa). When the DrinkAll method is called on passedBeer, and control returns to PassBeerByVal, we will find that myBeer has been modified. The called procedure was able to modify the beer object on the heap, because it had a reference to it. That may seem unfair, and I’ve heard more than one developer suggest that the heap object should be copied. That’s not a bad idea, but it would require the compiler to copy not only the object on the heap, but any other objects it references or contains. This type of copy is often referred to as a “deep copy” or “clone”, and if you want it done, you’ll need to implement it yourself. The implementation Microsoft chose makes sense, if you think about the potential performance implications of doing a deep copy. Deep copies could end up allocating lots of memory on the heap, and this memory only gets cleaned up when the garbage collector gets around to it.

 

Application Memory

Stack

Heap

Sa- myBeer [Ha]

Ha [Beer Object]

Sb- passedBeer[Ha]

 

Figure 4 (memory allocation for a reference type passed ByVal)

 

It might seem that ByVal is of little value for reference types, but that’s not the case. ByVal does not guarantee that the public properties and methods of the Beer Object (Ha) will not be used by HoldBeer(), but it does promise that after the call, myBeer will still refer to the same object (Ha). This can be just as useful, and The .NET framework itself takes advantage of it in a form’s Closing event.

 

Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs)

 

The CancelEventArgs object (e) is passed by value, but we are able to set e.Cancel = True, letting the form know not to close (see the .NET SDK for details if you’re not familiar with this event.

 

Hopefully, at this point you have a clearer understanding of the way ByVal works for reference types, and an appreciation for why it works the way that it does. Things can get much more complex if we introduce remoting into the picture, but that’s beyond the scope of this article.

 

 

I’ve written a simple demo that I think is a fun way to illustrate the concept to your friends. The Demo consists of a single form with several buttons…

 

 

The “New Beer” button creates a beer object after prompting you for your favorite brand. You may take a sip from your beer, or ask someone to hold it for you. You may use the HoldByVal button to pass your beer to the holder by value, or the HoldByRef button to pass it by reference. A look at the underlying code will show you that both buttons perform the same code (the only difference is whether the beer is passed by reference or by value). The code randomly selects one of four people to hold your beer. After that, a random action is selected. The beer you passed may be sipped, completely drank, or replaced with a fresh “Sam Adams” before it is returned to you. As you may suspect by now, when you pass your beer by value, you will never actually get a fresh beer back, but you’ll probably find that someone drank at least some of it!

 

Dim ht As New Hashtable

        ht.Add(1, "Joe")

        ht.Add(2, "Mary")

        ht.Add(3, "Phil")

        ht.Add(4, "Sam")

 

        Dim randObj As New Random

        Dim intI As Integer = randObj.Next(1, 4)

        m_GuiltyParty = ht.Item(intI)

 

        intI = randObj.Next(1, 4)

        Try

            Select Case intI

                Case 1

                    passedBeer.Sip()

                Case 2

                    passedBeer = New Beer("Sam Adams")

                Case Else

                    passedBeer.DrinkAll()

            End Select

        Catch

        End Try

        RefreshIndicator()

 

 

The full code for the form is below, Just save it as form1.vb and add it to your project. Have fun, and don’t drink too much!!


 

 

Public Class Form1

    Inherits System.Windows.Forms.Form

 

#Region " Windows Form Designer generated code "

 

    Public Sub New()

        MyBase.New()

 

        'This call is required by the Windows Form Designer.

        InitializeComponent()

 

        'Add any initialization after the InitializeComponent() call

 

    End Sub

 

    'Form overrides dispose to clean up the component list.

    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)

        If disposing Then

            If Not (components Is Nothing) Then

                components.Dispose()

            End If

        End If

        MyBase.Dispose(disposing)

    End Sub

 

    'Required by the Windows Form Designer

    Private components As System.ComponentModel.IContainer

 

    'NOTE: The following procedure is required by the Windows Form Designer

    'It can be modified using the Windows Form Designer. 

    'Do not modify it using the code editor.

    Friend WithEvents beerIndicator As System.Windows.Forms.ProgressBar

    Friend WithEvents cmdHoldByValue As System.Windows.Forms.Button

    Friend WithEvents lblBrand As System.Windows.Forms.Label

    Friend WithEvents cmdNewBeer As System.Windows.Forms.Button

    Friend WithEvents cmdSip As System.Windows.Forms.Button

    Friend WithEvents cmdHoldByRef As System.Windows.Forms.Button

    Friend WithEvents cmdWho As System.Windows.Forms.Button

    Private Sub InitializeComponent()

        Me.beerIndicator = New System.Windows.Forms.ProgressBar

        Me.cmdHoldByValue = New System.Windows.Forms.Button

        Me.cmdHoldByRef = New System.Windows.Forms.Button

        Me.cmdNewBeer = New System.Windows.Forms.Button

        Me.cmdSip = New System.Windows.Forms.Button

        Me.lblBrand = New System.Windows.Forms.Label

        Me.cmdWho = New System.Windows.Forms.Button

        Me.SuspendLayout()

        '

        'beerIndicator

        '

        Me.beerIndicator.Location = New System.Drawing.Point(32, 72)

        Me.beerIndicator.Name = "beerIndicator"

        Me.beerIndicator.Size = New System.Drawing.Size(256, 23)

        Me.beerIndicator.TabIndex = 0

        Me.beerIndicator.Value = 100

        '

        'cmdHoldByValue

        '

        Me.cmdHoldByValue.Location = New System.Drawing.Point(24, 136)

        Me.cmdHoldByValue.Name = "cmdHoldByValue"

        Me.cmdHoldByValue.Size = New System.Drawing.Size(112, 23)

        Me.cmdHoldByValue.TabIndex = 1

        Me.cmdHoldByValue.Text = "Hold ByVal"

        '

        'cmdHoldByRef

        '

        Me.cmdHoldByRef.Location = New System.Drawing.Point(24, 176)

        Me.cmdHoldByRef.Name = "cmdHoldByRef"

        Me.cmdHoldByRef.Size = New System.Drawing.Size(112, 23)

        Me.cmdHoldByRef.TabIndex = 2

        Me.cmdHoldByRef.Text = "Hold ByRef"

        '

        'cmdNewBeer

        '

        Me.cmdNewBeer.Location = New System.Drawing.Point(24, 40)

        Me.cmdNewBeer.Name = "cmdNewBeer"

        Me.cmdNewBeer.Size = New System.Drawing.Size(112, 23)

        Me.cmdNewBeer.TabIndex = 3

        Me.cmdNewBeer.Text = "New Beer"

        '

        'cmdSip

        '

        Me.cmdSip.Location = New System.Drawing.Point(152, 40)

        Me.cmdSip.Name = "cmdSip"

        Me.cmdSip.Size = New System.Drawing.Size(112, 23)

        Me.cmdSip.TabIndex = 4

        Me.cmdSip.Text = "Sip"

        '

        'lblBrand

        '

        Me.lblBrand.Location = New System.Drawing.Point(32, 104)

        Me.lblBrand.Name = "lblBrand"

        Me.lblBrand.Size = New System.Drawing.Size(256, 23)

        Me.lblBrand.TabIndex = 5

        '

        'cmdWho

        '

        Me.cmdWho.Location = New System.Drawing.Point(24, 224)

        Me.cmdWho.Name = "cmdWho"

        Me.cmdWho.Size = New System.Drawing.Size(224, 23)

        Me.cmdWho.TabIndex = 6

        Me.cmdWho.Text = "Who messed with my beer?"

        '

        'Form1

        '

        Me.AutoScaleBaseSize = New System.Drawing.Size(6, 15)

        Me.ClientSize = New System.Drawing.Size(312, 273)

        Me.Controls.Add(Me.cmdWho)

        Me.Controls.Add(Me.lblBrand)

        Me.Controls.Add(Me.cmdSip)

        Me.Controls.Add(Me.cmdNewBeer)

        Me.Controls.Add(Me.cmdHoldByRef)

        Me.Controls.Add(Me.cmdHoldByValue)

        Me.Controls.Add(Me.beerIndicator)

        Me.Name = "Form1"

        Me.Text = "Form1"

        Me.ResumeLayout(False)

 

    End Sub

 

#End Region

 

    Private m_Beer As Beer

    Private m_GuiltyParty As String

  

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        NewBeer("Budweiser")

    End Sub

 

    Private Sub cmdNewBeer_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdNewBeer.Click

        NewBeer(InputBox("What Brand?", "Brand Selector", "Budweiser"))

    End Sub

 

    Private Sub NewBeer(ByVal sBrand As String)

        m_Beer = New Beer(sBrand)

        Me.beerIndicator.Value = m_Beer.PercentFull

        Me.lblBrand.Text = m_Beer.BrandName

 

    End Sub

 

 

    Private Sub cmdSip_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdSip.Click

        Try

            m_Beer.Sip()

            RefreshIndicator()

        Catch

            MessageBox.Show("Your Beer is Empty!")

        End Try

 

    End Sub

 

    Private Sub cmdHoldByValue_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdHoldByValue.Click

        HoldBeerByVal(m_Beer)

    End Sub

    Private Sub cmdHoldByRef_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdHoldByRef.Click

        HoldBeerByRef(m_Beer)

    End Sub

 

    Private Sub HoldBeerByVal(ByVal passedBeer As Beer)

        Dim ht As New Hashtable

        ht.Add(1, "Joe")

        ht.Add(2, "Mary")

        ht.Add(3, "Phil")

        ht.Add(4, "Sam")

 

        Dim randObj As New Random

        Dim intI As Integer = randObj.Next(1, 4)

        m_GuiltyParty = ht.Item(intI)

 

        intI = randObj.Next(1, 4)

        Try

            Select Case intI

                Case 1

                    passedBeer.Sip()

                Case 2

                    passedBeer = New Beer("Sam Adams")

                Case Else

                    passedBeer.DrinkAll()

            End Select

        Catch

        End Try

        RefreshIndicator()

 

    End Sub

    Private Sub HoldBeerByRef(ByRef passedBeer As Beer)

        Dim ht As New Hashtable

        ht.Add(1, "Joe")

        ht.Add(2, "Mary")

        ht.Add(3, "Phil")

        ht.Add(4, "Sam")

 

        Dim randObj As New Random

        Dim intI As Integer = randObj.Next(1, 4)

        m_GuiltyParty = ht.Item(intI)

 

        intI = randObj.Next(1, 4)

        Try

            Select Case intI

                Case 1

                    passedBeer.Sip()

                Case 2

                    passedBeer = New Beer("Sam Adams")

                Case Else

                    passedBeer.DrinkAll()

            End Select

        Catch

        End Try

        RefreshIndicator()

 

 

    End Sub

    Private Sub RefreshIndicator()

        Me.beerIndicator.Value = m_Beer.PercentFull

        Me.lblBrand.Text = m_Beer.BrandName

    End Sub

 

 

 

    Private Sub cmdWho_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdWho.Click

        MessageBox.Show("'" & m_GuiltyParty & "' is Guilty!")

    End Sub

End Class

 

Public Class Beer

    Private m_sBrand As String

    Private m_iPercentFull As Integer

    Public Sub New(ByVal sBrand As String)

        m_sBrand = sBrand

        m_iPercentFull = 100

    End Sub

    Public Function DrinkAll()

        m_iPercentFull = 0

    End Function

    Public Function Sip()

        If Me.PercentFull <= 0 Then

            Throw New Exception("Beer is Empty!")

        End If

        PercentFull = PercentFull - 10

    End Function

    Public ReadOnly Property BrandName() As String

        Get

            Return m_sBrand

        End Get

    End Property

 

    Public Property PercentFull() As Integer

        Get

            Return m_iPercentFull

        End Get

        Set(ByVal Value As Integer)

            Select Case Value

                Case Is > 100

                    m_iPercentFull = 100

                Case Is < 0

                    m_iPercentFull = 0

                Case Else

                    m_iPercentFull = Value

            End Select

        End Set

    End Property

 

End Class

 

posted on Tuesday, December 28, 2004 1:55 PM

Feedback

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 12/29/2004 12:32 PM CoderDave
Wow - this clears up alot! Nice Job, MANY THANKS!

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 12/30/2004 2:25 PM MR
nice job Scott...very clear and easy to read...MR

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 1/15/2005 12:25 AM fabio
thanks!!! i thought i was going out of my mind

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 2/11/2005 1:25 AM rajesh
Its Really very useful for all the developers

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 2/17/2005 11:49 PM JM
I thought your explanation and example were first rate so much so that I stepped out to but a 6 pack.

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 3/21/2005 3:23 AM Deepesh
Good Beer ! , nice article , I felt like drunk.

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 3/23/2005 12:50 PM S.Gonell
Couldn't be explained better. well done.

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 4/8/2005 8:08 AM Hesh
Nicely Done!!

# re: ByVal Behavior With Reference Vs. Value Types (Who Drank My Beer?) 5/11/2005 8:48 AM Adrian
Good One! Cleared up some confusion.

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: