هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.
Contents
Contents of this article:
- Contents
- Introduction
- Passing Mechanism
- The Two Genres
- Value Types
- Reference Types
- Boxing and Unboxing
- Manual Boxing
- Manual Unboxing
- Automatic Boxing
- Automatic Unboxing
- Summary
Introduction
Today, we’ll have a brief discussion of value types and reference types in .NET framework and how their behavior change while used or passed to functions. We’ll talk about the passing mechanism, the two genres of .NET types, the scope, and the conversion routines between the two genres.
Passing Mechanism
When you pass an object to a function, this object can be passed either by value or by reference.
To pass an object by value means that a copy of the object is passed to the function not the object itself so changes to the object inside the function won’t affect your original copy.
On the other hand, passing an object by reference means passing that object itself so changes to the object inside that function is reflected on your original copy.
Consider the following example. Although the function changed the value of i, the change didn’t affect the original variable that’s because the variable is passed by value.
// C# static void Main() { int i = 10; int j = i; j = 5; // you expect '5' Console.WriteLine(i); // but i is still 10 !! // Now you try another way Sqr(i); // you expect '100' Console.WriteLine(i); // but i is still 10 !!! } static void Sqr(int i) { i *= i; }
' VB.NET Sub Main() Dim i As Integer = 10 Dim j As Integer = i j = 5 ' you expect '5' Console.WriteLine(i) ' but i is still 10 !!! ' now you try another way Sqr(i) ' you expect '100' Console.WriteLine(i) ' but i is still 10 !!! End Sub Sub Sqr(ByVal i As Integer) i = i * i End Sub
Now, let’s try something else. The following example passes i by reference.
// C# static void Main() { int i = 10; Sqr(ref i); // you expect '100' Console.WriteLine(i); // you are right! } static void Sqr(ref int i) { i *= i; }
' VB.NET Sub Main() Dim i As Integer = 10 Sqr(i) ' you expect '100' Console.WriteLine(i) ' you are right! End Sub Sub Sqr(ByRef i As Integer) i = i * i End Sub
Notice how the ref keyword in C# (ByRef in VB.NET) changed the overall behavior. Now, i itself is passed to our function, so the changes made in the function affected the original variable (both are the same.)
The Two Genres
Talking about passing object by value or by reference leads up to talk about the two major genres of types in .NET Framework:
- Value Types
- Reference Types
Value Types
Value types are those normally passed by value unless you explicitly specify the ref keyword (or ByVal in VB.NET) to override the default behavior and pass the object by reference.
Value types in .NET are those inherit -directly or indirectly- from System.ValueType including all structures, enumerations, and primitives (integers, floats, etc.)
The previous examples use an integer value that’s absolutely a value-type.
Value types are stored on the first section of the memory, the stack. Thus, they are removed from memory as soon as their scope ends. The scope marks the beginning and the end of object’s life (object is considered alive at the time you declare it.) See the following code the marks scopes inside a class.
// C# class ClassScope { // Scope 1 void Method1() { // Scope 1.1 { // Scope 1.1.1 { // Scope 1.1.1.1 } { // Scope 1.1.1.2 } } } void Method2() { // Scope 1.2 if (true) { // Scope 1.2.1 while (true) { // Scope 1.2.1.1 } } } }
' VB.NET Class ClassScope ' Scope 1 Sub Method1() ' Scope 1.1 End Sub Sub Method2() ' Scope 1.2 If True Then ' Scope 1.2.1 Do While True ' Scope 1.2.1.1 Loop End If End Sub End Class
Reference Types
Reference types are those normally passed by reference and never can be passed by value. Reference types include all objects other than value types, we mean all other classes inherit from System.Object -directly or indirectly- and don’t inherit from System.ValueType.
Reference types are stored in the other version of the memory, the heap, and you can’t determine when the object is removed from memory since the heap is fully managed by the memory manager of .NET, the GC (Garbage Collector.)
Now, let’s see the difference between value types and reference types in action. The following example instantiates two objects, one is a structure (value type) and the other is a class (reference types.) After that, both objects are passed to two functions, both try to change the contents of the objects. The changes of the function affect the reference type outside, while the other value type object outside the function doesn’t get affected.
// C# static void Main() { ValStruct valObj = new ValStruct(); RefClass refObj = new RefClass(); valObj.x = 4; valObj.y = 4; refObj.x = 4; refObj.y = 4; MultipleStruct(valObj); MultipleClass(refObj); Console.WriteLine("Struct:tx = {0},ty = {1}", valObj.x, valObj.y); Console.WriteLine("Class:tx = {0},ty = {1}", refObj.x, refObj.y); // Results // Struct: x = 4, y = 4 // Class: x = 8, y = 8 } static void MultipleStruct(ValStruct obj) { obj.x *= 2; obj.y *= 2; } static void MultipleClass(RefClass obj) { obj.x *= 2; obj.y *= 2; } struct ValStruct { public int x; public int y; } class RefClass { public int x; public int y; }
' VB.NET Sub Main() Dim valObj As New ValStruct() Dim refObj As New RefClass() valObj.x = 4 valObj.y = 4 refObj.x = 4 refObj.y = 4 MultipleStruct(valObj) MultipleClass(refObj) Console.WriteLine("Struct:tx = {0},ty = {1}", _ valObj.x, valObj.y) Console.WriteLine("Class:tx = {0},ty = {1}", _ refObj.x, refObj.y) ' Results ' Struct: x = 4, y = 4 ' Class: x = 8, y = 8 End Sub Sub MultipleStruct(ByVal obj As ValStruct) obj.x *= 2 obj.y *= 2 End Sub Sub MultipleClass(ByVal obj As RefClass) obj.x *= 2 obj.y *= 2 End Sub Structure ValStruct Public x As Integer Public y As Integer End Structure Class RefClass Public x As Integer Public y As Integer End Class
A little yet very important note: When comparing objects with the double equal signs (or the single sign in VB.NET,) objects are being compared internally using the System.Object.Equals() function. This function returns True if both value objects have the same value or both reference objects refer to the same object (doesn’t matter if both have the same value and are different objects.) Conversely, using the not equals operator (!= in C# and <> in VB.NET) uses the same comparison function, however, it reverses its return value (True becomes False and vice versa.)
Boxing and Unboxing
Boxing is the process of converting a value type into reference type. Unboxing on the other hand is the process of converting that boxed value type to its original state. Boxing and unboxing can be done manually (by you) or automatically (by the runtime.) Let’s see this in action.
Manual Boxing
Consider the following code:
// C# byte num = 25; object numObj = num;
' VB.NET Dim num As Byte = 25 Dim numObj As Object = num
The last code simply converted a value type (the byte variable) into reference type by encapsulating it into a System.Object variable. Does that really involve that passing the System.Object would be done by reference? Absolutely! (Check it yourself!)
Manual Unboxing
Now you have a boxed byte, how can you retrieve it later, i.e., restore it back to be a value type? Consider the following code:
// C# // Boxing byte num = 25; object numObj = num; // Unboxing byte anotherNum = (byte)numObj;
'VB.NET 'Boxing Dim num As Byte = 25 Dim numObj As Object = num 'Unboxing Dim anotherNum As Byte = CByte(numObj)
Beware not to try to convert a boxed value to another type not its original type.
Automatic Boxing
Boxing can be done automatically by the runtime if you tried to pass that value type to a function that accepts a System.Object not that value type.
// C# static void Main() { byte num = 25; // Automatic Boxing Foo (num); } static void Foo(object obj) { Console.WriteLine(obj.GetType()); Console.WriteLine(obj.ToString()); }
' VB.NET Sub Main() Dim num As Byte = 25 'Automatic Boxing Foo(num) End Sub Sub Foo(ByVal obj As Object) Console.WriteLine(obj.GetType) Console.WriteLine(obj.ToString) End Sub
Automatic Unboxing
The runtime can automatically unbox a boxed value:
// C# static void Main() { // Automatic Boxing object num = 25; // Automatic Unboxing – not really works Foo(num); } static void Foo(byte obj) { Console.WriteLine(obj.GetType()); Console.WriteLine(obj.ToString()); }
' VB.NET Sub Main() ' Automatic Boxing Dim num As Object = 25 ' Automatic Unboxing - works Foo(num) End Sub Sub Foo(ByVal obj As Byte) Console.WriteLine(obj.GetType) Console.WriteLine(obj.ToString) End Sub
The difference between C# and VB.NET in the last situation is that VB.NET allows automatic unboxing while C# doesn’t. (Theoretically, VB.NET allows automatic conversion between the vast majority of types, while C# lacks this feature.)
Summary
That was a brief discussion of value types and reference types in .NET Framework. Value types are those normally when passed to a function or copied, a copy of the value is used not the original value. Therefore, changes made to that copy don’t affect the original object.
On the other hand, reference types are those when passed to a function or copied, the object itself is used. Therefore, any changes made inside the function or to the copy do affect the original object (both are the same.)
Value types in .NET are all types inherit from System.ValueType including structures, enumerations, and primitives. All other classes don’t inherit from System.ValueType are reference types.
Boxing is the process of converting a value type to reference type. Unboxing is retrieving that boxed reference type back. To box a variable simple convert it to System.Object. To unbox it, convert it back to its original type. Boxing could also occur automatically when you pass a value type to a function that accepts System.Object not the type itself.