Contents

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.

' 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.

' 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.

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.

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:

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.

Overview

First, this writing concentrates of and compares between three programming languages, C#, C++/CLI, and ISO/ANSI C++. It discusses 9 rules that every developer should keep in mind while working with constructors, destructors, and finalizers and class hierarchies:

Rule #1: Contrsuctors are called in descending order

Rule #2: In C# lexicology, a destructor and a finalizer refer to the same thing

Rule #3: Destructors are called in ascending order

Rule #4: Finalizers are a feature of GC-managed objects only

Rule #5: You cannot determine when finalizers would be called

Rule #6: C++/CLI differs between destructors and finalizers

Rule #7: In C++/CLI and classic C++, you can determine when destructors are called

Rule #8: In C++/CLI, destructors and finalizers are not called together

Rule #9: Beware of virtual functions in constructors

Rule #1: Constructors are called in descending order

Rule #1: Constructors are called in descending order; starting from the root class and stepping down through the tree to reach the last leaf object that you need to instantiate. Applies to C#, C++/CLI, and ANSI C++.

When you define a class destructor with that C++-alike syntax (preceding the function name with a ~) the compiler actually replaces your destructor with an override of the virtual Object.Finalize() function. That is, before the object is removed (i.e. GC-collected) from the memory, the finalizer (i.e. destructor) is called first. This finalizer first executes your code. After that it calls the finalizer of the base type of your object. If we could decompile our assembly, we would see that our destructor in the ChildClass (so other classes) has been replaced with this function:

Rule #3: Destructors are called in ascending order

Rule #3: Destructors are called in ascending order, starting from the leaf object that you need to instantiate and moving up through the tree to reach the very first base class of your object. In reverse of constructors calling order. Applies to C#, C++/CLI, and ANSI C++.

Rule #4: Finalizers are a feature of GC-managed objects

Rule #4: Finalizers are a feature of GC managed objects (i.e. managed classes). That’s because the finalizer is called only when the GC removes the object from the memory (i.e. frees memory associated with).

Rule #5: You can’t determine when finalizers would be called

That’s because you don’t know when the next garbage collection would occur, even if you performed a manual garbage collection (using System.GC.Collect() function) you won’t know exactly when memory would be released. In addition, GC always delay releasing of finalizable object, it puts them in a special GC queue called freachable (pronounced ef-reachable, F stands for Finalize) queue. Applies to C# and C++/CLI (.NET.)

Rule #6: C++/CLI differs between destructors and finalizers

Rule #6: C++/CLI differs between destructors and finalizers. That is, finalizers are called by GC, and destructors are called when you manually delete the object.

The destructors are not called. Why? Unlike C#, in C++/CLI there is a big difference between destructors and finalizers. As you know, the finalizer is called when the GC removes the object from the memory. Destructors, on the other hand, are called when you destroy the object yourself (e.g. use the delete keyword.)

Now, try to change the test code to the following:

int main()
{
ChildClass^ cls = gcnew ChildClass();
delete cls;
}

Run the code. Now, destructors are called.

Next, let’s add finalizers to our objects. The code should be like the following:

As you see, the syntax of constructors, destructors, and finalizers are very similar.

Now, let’s try the code:

int main()
{
ChildClass^ cls = gcnew ChildClass();
}

GC would call finalizers and the code would outputs the following:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
finz of ChildClass
finz of DerivedClass
finz of BaseClass

Rule #7: In C++/CLI and C++, you can determine when destructors are called

Now, try to destroy the object yourself:

int main()
{
ChildClass^ cls = gcnew ChildClass();
delete cls;
}

The delete statement calls object destructors and removes the object from memory.

Or else, declare the object with stack-semantics:

int main()
{
ChildClass cls;
}

Now, destructors are called when the scope of the object ends.

Rule #8: In C++/CLI, destructors and finalizers are not called together

Rule #8: In C++/CLI, destructors and finalizers are not called together. Only destructors or finalizers are called. If you manually delete the object or you declare it with stack-semantics, destructors are called. If you leaved the object for GC to handle, finalizers are called.

Now try to run the code. The code should outputs the following results:

ctor of BaseClass
ctor of DerivedClass
ctor of ChildClass
dtor of ChildClass
dtor of DerivedClass
dtor of BaseClass

Rule #9: Beware of virtual functions in constructors

Rule #9: Beware of virtual (overridable) functions in constructors. In .NET (C# and C++/CLI,) the overload of the most derived object (the object to be instantiated) is called. In traditional C++ (ISO/ANSI C++,) the overload of the current object constructed is called.