Understanding Value Types and Reference Types

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

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.

Similar Posts:

Random Posts:

Recent Posts: