Thursday, September 9, 2010

PASSING PARAMETERS BY VALUE & BY REFERENCE

I misunderstood passing a reference type to a method by reference and wrote it as ref param.  I found this error in understanding when I asked a question about memory footprint of a reference type variable.  The forum folks pointed out that I did not need to pass my object via ref and gave me some great links to help me wrap my brain around my misunderstanding.

I spent time researching and then testing code to make sure I got it.  But what I realized after reading the articles referenced at the end of this article, although each is well-written, they were not targeted to the beginner.  So this blog post takes a crack at explaining this concept in more basic, beginner terms.

1. PREFACE:
I need to lay out some code that we'll use as our example throughout the article. The links above use StringBuilder as the example reference type. I think I'll go a step further and use a class as my reference type, as newbies think in terms of classes at the beginning as that's how the books lay it out for us.  I think I'll use a simple example of a Book as my reference type (my object):
   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace ParameterPassing
   7: {
   8:     enum CoverType
   9:     {
  10:         Hardback,
  11:         Softback
  12:     }
  13:  
  14:     enum ClassType
  15:     {
  16:         Spy,
  17:         Drama,
  18:         Romance,
  19:         Historical
  20:     }
  21:  
  22:     class Book
  23:     {
  24:         public string Author { get; set; }
  25:         public string Title { get; set; }
  26:         public int NumberPages { get; set; }
  27:         public CoverType Cover { get; set; }
  28:         public ClassType Class { get; set; }
  29:  
  30:         //...
  31:  
  32:     }
  33: }

1.1 Reference vs. Value Types:
There are two types: reference and value types.

1.1.1 Value Types:
Value types are simple types (boolean, integer, byte, float, long, decimal, char, etc.), struct, and enum. A value type holds its value in its variable and is stored on the stack memory.

So consider the following code. What would the value of  copyNum be?  How about num?


   1: int num = 10;
   2: int copyNum = num;
   3: num++

The result is num = 11 and copyNum = 10. Remember, each variable directly and separately hold their own value. Therefore, changing num after its been copied to copyNum does not increment copyNum.

1.1.2 Reference Types:
Reference types are known as objects. They include: class, arrays, interface, delegate, built-in reference types (e.g. object, string, and dynamic), and nullable types. Unlike a value type which holds its data in the variable, a reference type holds the heap's memory location of its corresponding object. So when you see referenced to or address of, it means the memory location in the heap.

Did I lose you? If so, let's step back one step. Recall that the variable holding the heap's memory location (or address or reference) is stored in the stack memory and that the actual object is stored in the heap memory. The variable simply holds the memory location and no data.

An example:
     Book myBook = new Book();

Here's what happens with the above line of code: First the compiler creates the variable myBook on the stack. Then the new keyword creates the object on the heap, which is where the data (its properties and methods) for object will reside. The heap's memory location (address) is then stored in the variable myBook.

Let's now move forward a couple of steps.  What happens if we copy a reference type to another without using the new keyword?

     Book myBook = new Book();
     Book copyBook = myBook;

Any guesses? Think about it for a minute.

We didn't use the new keyword; that means an object is not created on the heap for copyBook. Right? So what does the variable copyBook have in it? It has a copy of the heap's memory location to the original myBook object.




 


2. PASSING PARAMETERS:
There are four types of parameters: value, reference, output, and parameter arrays, all of which can be used with both value and reference types. I'm only going to cover the first two.

2.1 Passing Value Parameters for Value Types:
This is the default of how parameters are passed. For example, in the following method, myBook.NumberPages is passed into the method by value because no other parameter keyword is specified (i.e. ref, out, or params).


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace ParameterPassing
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             Run();
  13:         }
  14:         public static void Run()
  15:         {
  16:             Book myBook = new Book();
  17:             myBook.NumberPages = 250;
  18:             Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
  19:  
  20:             Change(myBook.NumberPages);
  21:  
  22:             Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
  23:             Console.ReadLine();
  24:         }
  25:  
  26:         public static void Change(int numPages)
  27:         {
  28:             numPages = 100;
  29:             Console.WriteLine("numPages: {0}", numPages);
  30:         }
  31:     }
  32: }


The Console reads out:
     myBook.NumberPages: 250
     numPages: 100
     myBook.NumberPages: 250

So what happens?


Change() is called and comes into scope. The parameter numPages is created on the stack as a separate integer (value type) and it contains a copy of the value in myBook.NumberPages. Both numPages and myBook.NumberPages are separate blocks of memory on the stack and each holds its own value. That means any changes to numPages will not change myBook.NumberPages. . Therefore, when numPages is changed to equal 100, myBook.NumberPages remains equal to 250.

 


 


2.2 Passing Reference Parameters for Value Types:
What if we want to be able to change the original variable with our passed parameter? For example, using the code above, we want Change() to update originalNumPages. To do this, place the ref keyword before originalNumPages and the method's parameter, as shown:


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace ParameterPassing
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             Run();
  13:         }
  14:         public static void Run()
  15:         {
  16:             Book myBook = new Book();
  17:             myBook.NumberPages = 250;
  18:             //Can't ref a property; therefore, we'll use an int
  19:             int originalNumPages = myBook.NumberPages;
  20:             Console.WriteLine("originalNumPages: {0}", originalNumPages);
  21:  
  22:             Change(ref originalNumPages);
  23:  
  24:             Console.WriteLine("originalNumPages: {0}", originalNumPages);
  25:             Console.ReadLine();
  26:         }
  27:  
  28:         public static void Change(ref int numPages)
  29:         {
  30:             numPages = 100;
  31:             Console.WriteLine("numPages: {0}", numPages);
  32:         }
  33:     }
  34: }


The Console reads out:
     originalNumPages: 250
     numPages: 100
     originalNumPages: 100

The result would be that both numPages and originalNumPages are both changed to a value of 100.
-------------------------------------------------------------------------------------------------

Side Note: You will notice that I added int originalNumPages = myBook.NumberPages; and placed originalNumPages as the variable to pass by reference to Change(). Why? Because you  can't pass an object's property by reference.  So I had to modify my code a bit to illustrate the point.
-------------------------------------------------------------------------------------------------

So what happened? 

Instead of creating a new block of memory on the stack for numPages, the method uses originalNumPages variable's memory location. Let me say that a different a way, just to make sure it's clear. numPages contains the originalNumPage's stack memory location So it's passing by reference (memory location) and not by value. Ah, the naming convention makes sense, right?









 


2.3 Passing Value Parameters for Reference Types:
Here is where you might get confused, as this is where I had my misunderstanding (which I explain in the conclusion below). Remember, reference type variables hold the heap's memory location (or address) to the actual object (Book being my object). We covered that above. When we pass a reference type by value to a method, we are passing a memory location to a newly created parameter, which is created on the stack. Did I lose you? Here's an example:


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace ParameterPassing
   7: {
   8:     class Program
   9:     {
  10:         static void Main(string[] args)
  11:         {
  12:             Run();
  13:         }
  14:         public static void Run()
  15:         {
  16:             Book myBook = new Book();
  17:             myBook.NumberPages = 250;
  18:             Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
  19:  
  20:             Change(myBook);
  21:  
  22:             Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
  23:             Console.ReadLine();
  24:         }
  25:  
  26:         public static void Change(Book bookObj)
  27:         {
  28:             bookObj.NumberPages = 100;
  29:             Console.WriteLine("bookObj.NumberPages: {0}", bookObj.NumberPages);
  30:         }
  31:     }
  32: }


So what happens?
When myBook is passed to the parameter bookObj, a new block of memory is created on the stack for bookObj and the value in myBook's variable is passed (copied) to bookObj parameter. Remember that value in myBook's variable is the object's heap memory location. (I need to stress that only the object's heap memory location is passed and not the object, as the object lives on the heap.) Now both myBook and bookObj have the same value (reference to the Book object's memory location).


What happens when the method changes a property in bookObj? The property is changed in the actual object, which means both bookObj and myBook are changed and are equal. After running the program, the Console reads:

     myBook.NumberPages: 250
     bookObj.NumberPages: 100
     myBook.NumberPages: 100

Do you understand why both bookObj.NumberPages and myBook.NumberPages are equal to 100? Because they are both addressed to (referenced to) the same object.

2.3.1 Proof - Examine Memory Addresses:
Since I'm talking in terms of memory locations, I thought it would help to grab and read each out on the Console.  In order to do that though, we need to modify and add some code to our example.  Please note, that the code and concepts here are above and beyond the beginner level. But I still wanted to provide them to assist you in your learning process.

C# is a managed software and the garbage collector moves around memory. Therefore we need to pin down what the variable we want in order to evaluate it.  We also have to make our Book class blittable (for more information on what that is, go to MSDN). We also modify our Program class to pin, get the handle, and then get the address of the pinned object.


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Runtime.InteropServices; //Added
   6:  
   7: namespace ParameterPassing
   8: {
   9:     #region "enums"
  10:     //enum CoverType
  11:     //{
  12:     //    Hardback,
  13:     //    Softback
  14:     //}
  15:  
  16:     //enum ClassType
  17:     //{
  18:     //    Spy,
  19:     //    Drama,
  20:     //    Romance,
  21:     //    Historical
  22:     //}
  23:     #endregion
  24:  
  25:     //Our class is now blittable
  26:     [StructLayout(LayoutKind.Sequential)]
  27:     class Book
  28:     {
  29:         //public string Author { get; set; }
  30:         //public string Title { get; set; }
  31:         public int NumberPages { get; set; }
  32:         //public CoverType Cover { get; set; }
  33:         //public ClassType Class { get; set; }
  34:  
  35:         //...
  36:  
  37:     }
  38: }


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Runtime.InteropServices; //Added
   6:  
   7: namespace ParameterPassing
   8: {
   9:     class Program
  10:     {
  11:         static void Main(string[] args)
  12:         {
  13:             Run();
  14:         }
  15:         public static void Run()
  16:         {
  17:             Book myBook = new Book();
  18:             /* In order to get the memory address of myBook,
  19:              * we have to "pin it" first.
  20:              */
  21:             GCHandle myHandle = GCHandle.Alloc(myBook,
  22:                 GCHandleType.Pinned);
  23:             IntPtr myBookAddress = myHandle.AddrOfPinnedObject();
  24:             myHandle.Free();
  25:  
  26:             Console.WriteLine("myBook address: {0}", myBookAddress);
  27:  
  28:             Change(myBook);
  29:  
  30:             //Repeat code to see if address changed
  31:             myHandle = GCHandle.Alloc(myBook,
  32:                 GCHandleType.Pinned);
  33:             myBookAddress = myHandle.AddrOfPinnedObject();
  34:             myHandle.Free();
  35:  
  36:             Console.WriteLine("myBook address: {0}", myBookAddress);
  37:             Console.ReadLine();
  38:         }
  39:  
  40:         public static void Change(Book bookObj)
  41:         {
  42:             GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
  43:                 GCHandleType.Pinned);
  44:             IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
  45:             bookObjHandle.Free();
  46:  
  47:             Console.WriteLine("bookObj address: {0}", bookObjAddress);
  48:         }
  49:     }
  50: }

The Console output is:
     myBook address: 11762472
     bookObj address: 11762472
     myBook address: 11762472

Notice that the memory addresses do not change.

2.3.2 Proof - Now What Happens If I Change bookObj to Another Book Object?
What happens if bookObj is instantiated to another Book object?



   1: public static void Change(Book bookObj)
   2: {
   3:     GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
   4:         GCHandleType.Pinned);
   5:     IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
   6:     bookObjHandle.Free();
   7:  
   8:     Console.WriteLine("bookObj address: {0}", bookObjAddress);
   9:  
  10:     //What happens if I create another Book object?
  11:     bookObj = new Book();
  12:  
  13:     bookObjHandle = GCHandle.Alloc(bookObj,
  14:         GCHandleType.Pinned);
  15:     bookObjAddress = bookObjHandle.AddrOfPinnedObject();
  16:     bookObjHandle.Free();
  17:  
  18:     Console.WriteLine("bookObj address: {0}", bookObjAddress);
  19:  
  20: }

The Console output is:
     myBook address:  11762472
     bookObj address:  11762472
     bookObj address:  11765252
     myBook address:  11762472

Interesting bookObj is now referenced to a different memory location (i.e. a different object on the heap), while myBook retains its original memory location. Therefore, we have two Book objects on the heap now and both are referenced.

2.4 Passing Reference Parameters for Reference Types:
Looking at the last proof in 2.3.2, what if we want to change the object that both variables are referenced to? We do that by passing the parameter by reference and using the ref keyword.



   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Runtime.InteropServices; //Added
   6:  
   7: namespace ParameterPassing
   8: {
   9:     class Program
  10:     {
  11:         static void Main(string[] args)
  12:         {
  13:             Run();
  14:         }
  15:         public static void Run()
  16:         {
  17:             Book myBook = new Book();
  18:             /* In order to get the memory address of myBook,
  19:              * we have to "pin it" first.
  20:              */
  21:             GCHandle myHandle = GCHandle.Alloc(myBook,
  22:                 GCHandleType.Pinned);
  23:             IntPtr myBookAddress = myHandle.AddrOfPinnedObject();
  24:             myHandle.Free();
  25:  
  26:             Console.WriteLine("myBook address: {0}", myBookAddress);
  27:  
  28:             Change(ref myBook);
  29:  
  30:             //Repeat code to see if address changed
  31:             myHandle = GCHandle.Alloc(myBook,
  32:                 GCHandleType.Pinned);
  33:             myBookAddress = myHandle.AddrOfPinnedObject();
  34:             myHandle.Free();
  35:  
  36:             Console.WriteLine("myBook address: {0}", myBookAddress);
  37:             Console.ReadLine();
  38:         }
  39:  
  40:         public static void Change(ref Book bookObj)
  41:         {
  42:             GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
  43:                 GCHandleType.Pinned);
  44:             IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
  45:             bookObjHandle.Free();
  46:  
  47:             Console.WriteLine("bookObj address: {0}", bookObjAddress);
  48:  
  49:             //What happens if I create another Book object?
  50:             bookObj = new Book();
  51:  
  52:             bookObjHandle = GCHandle.Alloc(bookObj,
  53:                 GCHandleType.Pinned);
  54:             bookObjAddress = bookObjHandle.AddrOfPinnedObject();
  55:             bookObjHandle.Free();
  56:  
  57:             Console.WriteLine("bookObj address: {0}", bookObjAddress);
  58:  
  59:         }
  60:     }
  61: }

The Console reads out:
     myBook address:  11762472
     bookObj address:  11762472
     bookObj address:  11765252
     myBook address:  11765252

So what happened here?




Since we are passing by reference, myBook's memory location on the stack is passed to the parameter bookObj. Think about that a moment. That means bookObj is referenced to myBook directly, which is referenced to the Book object.



When bookObj is instantiated to a new Book object (i.e. using the new keyword), the Book object is first created on the heap and then its memory location is stored in bookObj. Since bookObj is referenced to myBook, the new Book object's location is actually stored in myBook.


Effectively using ref allowed us to change from one object to another.




3 REFERENCE SITES:

Here are the sites that I used in my research:



http://rapidapplicationdevelopment.blogspot.com/2007/01/parameter-passing-in-c.html


4 CONCLUSION:

I hope this article was helpful for you. You might be wondering what was my misunderstanding that led me to this discovery. Well I thought that to change an object within a method, I had to pass it by reference. I didn't realize that was not necessary and actually frowned upon in the way I was using it. Rather, passing by value was all I needed and the methods were able to change the object just as I had intended.


Thank you for reading. I welcome your comments.

kick it on DotNetKicks.com

Shout it

No comments:

Post a Comment

About

My Photo
Welcome to my blog! This blog is about my journey to become a Master .NET coder and teacher.

Followers

Share it