I am a software engineer. I have been working with C++, MFC, and .net technologies for 15 years. I like video games and reading books.
In CSharp there are two major groups of Types. One is Predefined Primitive Data Types and other one is Class Types. We often hear that the former is Value Type and the later one is Reference Type. In this Article, we will explore how these Types behave when they are passed to a function as Value and as Reference.
2. The Point2D Class
This class contains two member variables (x, y). These members represent the co-ordinate of a point. A constructor which takes two parameters from the caller initializes these two members. We use SetXY function to make a modification to the members. The print function writes current co-ordinate to the Console Output window.
We will create instances of these class to explore various parameter passing techniques. Code for this class is shown below:
We will introduce one more class called TestFunc. This is a static class and will have all our Test Function for exploring various parameter passing methods. The skeleton of the class is below:
3. Primitive Types
A Primitive Type is a pre-defined data type that comes with the language and it directly represents a basic data like an integer or a character. Take a look at the below piece of code:
In the above function, we have only one variable called F. The local stack frame of the function AFunctionX allocates space for the variable F to store the value of 15. Look at the below depiction
In the above picture, we can see that the stack frame knows existence of a variable, p by its base address (For example, 0x79BC) on the stack frame and maps that to the actual address location 0x3830 on the same stack frame at a certain offset. The value 20 assigned in the function is stored at Stack Memory Location, 0x3830. We call this as a Variable Name Binding or simply "Name Binding". Here the name p is bound to the address 0x3830. Any read or write request on p takes place on the memory location 0x3830.
Now let us explore various ways of passing primitive data types to a function and its behavior.
3.1 Primitive Types - Pass by Value
We define the below function in the TestFunc static class. This function takes an integer as an argument. Inside the function we change the value of the argument to 15.
We call above defined function from our main program. First, we declare and initialize an integer variable. Before making a call to the function, the value of the integer is 20 and we know that the function changes this value to 15 inside its body.
The output of this simple code is given below:
Here, the function PassByValFunc changes the passed in parameter value from 20 to 15. Once, function returns, the main still preserves the value 20. Now, look at the below depiction.
First, we will look at the top portion of the picture. The picture shows that our execution stays at first statement which highlighted in yellow. At this stage, the call stack main has a name p defined at 79BC which binds to location 3830. Before calling this function, main program used the name p to assign a value of 20 in the memory location 3830 which stack frame. The called function defines name x inside its own stack frame at location 9796 and that bind to the memory location 773E. Since the parameter is passed by value, a copy occurs between p to x. In other words, the content of location 3830 is copied to the location 773E.
Now, we will explore the bottom portion of picture. The execution moves to the last statement. By this time, we already executed the assignment (x=15) and hence the content of 773E is changed to 15. But, the Stack Frame location 3830 of main is not modified. This is why we see main printing p as 20 after the function call.
3.2 Primitive Types - Pass by Reference with Ref Keyword
In the previous section, we saw passing an argument by value and we actually passed a primitive type as a parameter. Now, we will examine the behavior by sending the same primitive data type as a reference. We wrote a function in our static class to receive the argument By Reference. The code is below:
We should note the usage of the "ref " keyword in the function Argument List. In this function, we are changing the passed in value to 45 and printing the content of the name x before and after modifying it. Now, we write a calling code in the main program which is shown below:
Here, we are first assign an integer variable with a value of 15. After this, we call the function and pass the variable by reference. We should note the usage of the keyword ref here. We need to specify the ref keyword both in the Called Function's Argument List as well as Parameter List of calling code. The below screenshot shows the output of this piece of code:
By looking at the output, we may wonder why Main function is printing value of r is 45 which was changed in the called function, not in the Main function. Now, we will explore it. Remember, we passed the parameter by reference and have a look at below depiction:
The top portion of the picture shows that execution stays at the top of the function before changing the value of x. At this stage, Main stack frame address 3830 is associated to name r and holds a value 15. There is no difference here when we pass the parameter By Value or By Reference. But, in the called function Stack Frame, no memory is reserved for x. Here, x also binds to the calling stack location 3830 because of the mention of the ref keyword. Now memory location of Main function stack frame 3830 is bound by two names r and x.
Now, we will explore the bottom portion of the depiction. Execution stays at the end of the function and it changed stack frame location to 45 through the name x. Since x and r both binds to memory location 3839, we see main function printing 45 in the output result. So, when we pass a primitive type variable as a reference, the content changed in the called function gets reflected in the main function. Note, the binding (x binding to location 3830) will get scraped after the function returns.
3.3 Primitive Types - Pass by Reference with Out Keyword
When we pass a parameter By Reference with mention of “ref” keyword, the compiler expects that the parameter was already initialized. But, in some situation, the calling function just declares a primitive type and it will get assigned first in the called function. To handle this situation, c-sharp introduced the “out” keyword which be specified in the function signature and while calling that function.
Now, we can write below given code in our static class:
Here, in the code we assign a value 10 to the local variable x and then printing the value. This works same like the pass by reference. To pass a variable without initializing, we marked the parameter x with the “out” keyword. The out keyword expects that function must assign a value to x before it returns. Now, let we write the calling code as shown below:
The variable t is declared here and then we call the function. We pass the parameter t with the keyword out. This tells the compiler that the variable may not be initialized here and the function will assign a valid value to it. Since, “out” acts as pass by reference, the assigned value in the called function can be seen here. The output of the code is below:
4. Reference Types
When we say Reference Type, we mean that the memory location of data is stored by the type. All class instance that we create in C-sharp is reference type. For better understanding, we will look at the code given below
In the code, we are creating an instance of class MyClass and stored its reference in obj. Using this variable obj, we can access the members of the class. Now, we will look at the depiction below:
The name obj maintained by the Stack Frame of function (AFunctionX), binds that to the location 3830. Unlike primitive data type, the memory location holds address of some other memory location. Hence, we call obj as Reference Type. Note that in Value Type, the location should have been assigned with a direct value (Ex: int x=15).
When we create “Class Objects” using the keyword new or any other types with new, the memory will be claimed at the heap location. In our example, memory required for the object of type MyClass is allocated in the heap at location 5719. The variable obj holds the memory location of that heap and memory required to hold that address is given in the stack (3830). Since the name obj holds or refers address of the heap location, we call it as Reference Type.
4.1 Reference Type - Pass by Value
Now, we will explore Pass By Value for a Reference Type. We will write a function in our static class for that. The function is given below:
This function receives two arguments. By this time, we can answer that first parameter is a Reference Type and the second one is Value Type. When mode is zero, we try to change data members of the Point2D instance. This means, we are changing the heap memory content. When mode is one, we try to allocate new Point2D object and hold that in the variable called theobj. This means, we try to change the stack location to hold the new address. Alright! Now, we will look at the calling code:
In the calling code, first we allocate the Point2D object on the heap and initializing the point co-ordinates to 5 and 10. Then, we are passing the reference to this object (One) by value to the function PassByValFunc.
4.1.1 Changing the Content
The second argument passed to the function is zero. The function sees, mode as zero and changes the co-ordinate values to 7 and 8. Have a look at the below depiction:
We will look at the top half of the picture. Since we pass the reference (One) by value, the function allocates new location in the stack at 0x773E and stores the address of the heap location 0x3136. At this stage (When execution is at the if conditional statement which is highlighted above), there are two reference pointing to the same location 0x3136. In modern programming language like C-Sharp and Java, we say Reference-Counting for the heap location is two. One is from the Calling function through reference One and other one is from the called function through reference theObj.
The bottom portion of the picture shows that the content of the heap is changed through the reference theObj. The call we made to the function Setxy changed the content of the Heap location which is pointed by two reference objects. When the function returns, in the calling function we refer this changed heap memory location through Name “One” which bound to 0x3830. This is how the calling function prints 7 and 8 as co-ordinate values.
The output of the above shown code is below:
4.1.2 Changing the Reference
In the previous section, we asked the function to change the Value of the heap by passing zero as a value for the Mode argument. Now, we request the function to change the reference itself. Have a look at the calling code below:
To explain what is happening inside the function, we need to look at the depiction below:
When mode is 1, we allocate new heap and assign that to the local name, “theObj”. Now, we will look at the top portion of the picture. Everything is same as in the previous section as we do not touch the reference, “theObj”.
Now, look at the bottom portion of the picture. Here, we allocate the new heap at location 0x7717 and initialize the heap with co-ordinate values 100, 75. At this stage, we have two name bindings called “One” and “theObj”. The name “One” belongs to calling stack binding to the location 0x3830, which points to old heap location 0x3136. Name “theObj” belongs to called Stack Frame binding to the location stack location 0x773E which points to heap location 0x7717. The code output shows 100,75 inside the function and 5,10 after we return from it. This because we read location 0x7717 inside the function and after we return we read the location 0x3136.
Note, once we return from the function, the stack frame for the function is cleared and there by the stack location 0x773E and the address 0x7717 stored in it. This reduces the Reference Count for the location 0x7717 from 1 to zero signaling the Garbage Collector that the heap location is 0x7717 is not in use.
The output of executing the code is given in the below screenshot:
4.2 Reference Type - Pass by Reference
In the previous section we examined passing an Object Reference “By Value” to a function. We will explore passing the Object Reference “By Reference”. First, we will write a function in our static class and the code for it given below:
Note, we specified ref keyword in the as part of the first parameter. It tells the compiler that the Objects reference is passed “By Reference”. We know what happens when we pass a Value-Type (Primitive Types) By Reference. In this section, we examine the same for Reference Types using our Point2D object references. The calling code of this function is given below:
4.2.1 Changing the Content
Here, we do the same. But, at line 11, we pass the object reference “Two” with “ref” keyword. Also, we set the mode as 0 to examine the behavior of the changes in the heap content. Now, look at the below depiction:
Top portion of the picture shows there are two Name Bindings to the Calling Stack location 0x3830. The name “Two” binds to its own Call Stack location 0x3830 and the name “theObj” from the called function also binds to this same location. The stack location 0x3830 contains address of the heap location 0x3136.
Now, we will look at the bottom portion. We called the SetXY function with new co-ordinate values 7,8. We use the name “theObj” to write into the Heap Location 0x3136. When the function returns, we read the same heap content using the name “Two”. Now, we are clear why we get 7,8 as co-ordinate values from the calling code after the function returns. The code output is below:
4.2.2 Changing the Reference
In the previous section, we changed the Heap Content and examined the behavior. Now, we will change the Stack Content (i.e.) we allocate a new heap and store the address in the Same Stack location. In the calling code we are setting the mode as 1 as shown below:
Now, look at the below illustration:
Now, look at the top portion of the picture. Once we enter the function, the heap location has two reference count Two, theObj. The bottom portion shows the snapshot of memory when execution stays at print function. At this stage, we allocated a new object in the Heap at location 0x7717. Then, stored this heap address through “theObj” name binding. The calling stack location 0x3830 (Remember it has two Name-Bindings Two, theObj) now stores new heap location 0x7717.
Since, the old heap location is overwritten by the new address 0x7717 and nobody points to it, this old heap location will be garbage collected. The code output is shown below:
4.3 Reference Type - Pass by Reference with Out Keyword
The behavior is same like previous section. Since, we specify "out" we can pass the reference without initializing it. The object will be allocated in the called function and given to the caller. Read the out behavior from the Primitive Types sections. The complete code example is given below.
The keywords ref and out deals with how the stack location “Name-Binding” can be done. When we do not specify ref or out keywords, the parameter binds to a location in the called stack and a copy will be performed.
© 2018 sirama