Understanding Value Types and Reference Types


Unlike arrays, strings, or enumerations, C# structures do not have an identically named representation on in the .NET Core library(i.e. there is no System.Structure class) but are implicitly derived from System.ValueType. The role of System.ValueType is to ensure that the derived type (e.g., any structure) is allocated on the stack, rather than the garbage-collected heap.


Data allocated on the stack can be created and destroyed quickly, as its lifetime is determined by the defining scope. Heap-allocated data, on the other hand, is monitored by the .NET Core garbage collector and has a lifetime that is determined by many factors.



Functionally, the only purpose of System.ValueType is to override the virtual methods defined by System.Object to use value-based versus reference-based semantics.


// Structures and enumerations implicitly extend System.ValueType.
public abstract class ValueType : object
 {
     public virtual bool Equals(object obj);
     public virtual int GetHashCode();
     public Type GetType();
     public virtual string ToString();
 }


The lifetime of a structure (which includes all numerical data types [int,float] as well as any enum or structure) is predictable. When a structure variable falls out of the defining scope, it is removed from memory immediately.


// Local structures are popped off the stack when a method returns.
static void LocalValueTypes()
 {
     // Recall: "int" is really a System.Int32 structure.
     int i = 0;
     // Recall: Point is a structure type
     Point p = new Point();
 } // i and p popped off the stack here.


Using ValueTypes, Reference Types and the Assignment Operator

When you assign one value type to another, a member-by-member copy of the field data is achieved. In the case of a simple data type such as System.Int32, the only member to copy is the numerical value. However, in the case of your Point, the X and Y values are copied into new structure variable.


// Assigning 2 intrinsic value types results in 2 indepement variables in the stack.
static void ValueTypeAssignment()
 {
     Point p1 = new Point(10, 10);
     Point p2 = p1;
     p1.Display(); // 10 10
     p2.Display(); // 10 10
     // Change p1.x and print again. P2.x is not changed.
     p1.X = 100;
     Console.WriteLine("\n changed p1.X\n");
     p1.Display(); // 100 10
     p2.Display(); // 10 10
 }


In stark constract to value types, when you apply the assignment operator to reference types (meaning all class instances), you are redirecting what the reference variable point to in memory.


// Classes are always reference types.
class PointRef
 {
     public int X;
     public int Y;
     public PointRef(int xPos,int yPos)
     {
         X = xPos;
         Y = yPos;
     }
     public void Display()
     {
         Console.WriteLine(X + " " + Y);
     }
 }
 
static void ReferenceTypeAssignment()
 {
     PointRef p1 = new PointRef(10,10);
     PointRef p2 = p1;
     p1.Display();
     p2.Display();
    // Change p1.x and print again.
     p1.X = 100;
     p1.Display();
     p2.Display();
 }


You have 2 references pointing to the same object on the managed heap. Therefore, when you change the value of X using the p1 reference, p2.X reports the same value.



Passing Reference Types by Value

class Person
 {
     public string personName;
     public int personAge;
     public Person(string name,int age)
     {
         personName = name;
         personAge = age;
     }
     public Person()
     {
             
     }
     public void Display()
     {
         Console.WriteLine(personName+" "+personAge);
     }
 }
 
static void Main(string[] args)
 {
     Console.WriteLine("****** Passing Person object by value ******");
     Person fred = new Person("Fred",12);
     fred.Display();
     SendAPersonByValue(fred);
     Console.WriteLine("\nAfter by value call, Person is:");
     fred.Display();
     Console.ReadLine();
 }
static void SendAPersonByValue(Person p)
 {
     p.personAge = 99;
     p = new Person("Nicki", 21);
 }


Given that you were able to change the state of the incoming Person what was copied? The answer: a copy of the reference to the caller's object.

Therefore, as the SendAPersonByValue() method is pointing to the same object as the caller, it is possible to alter the object's state data. What is not possible is to reassign what reference is pointing to.



Passing Reference Types by Reference


class SimpleCSharpApp
 {
     static void Main(string[] args)
     {
         Console.WriteLine("****** Passing Person object by value ******");
         Person fred = new Person("Fred",34);
         fred.Display();
       SendAPersonByReference(ref fred);
         Console.WriteLine("\nAfter by value call, Person is:");
         fred.Display(); // Nicki 21
         Console.ReadLine();
     }
   static void SendAPersonByReference(ref Person p)
     {
         p.personAge = 99;
         // p is now pointing to a new object on the heap.
         p = new Person("Nicki", 21);
     }
 }
class Person
 {
     public string personName;
     public int personAge;
     public Person(string name,int age)
     {
         personName = name;
         personAge = age;
     }
     public Person()
     {
             
     }
     public void Display()
     {
         Console.WriteLine(personName+" "+personAge);
     }
 }



The golden rule to keep in mind when passing reference types is the following:


  1. If a reference type is passed by reference, the callee may change the values of the objects state data, as well as the object it is referencing.
  2. If a reference type is passed by value, the callee may change the values of the object's state data but not the object it is referencing.



Final Details Regarding Value Types and Reference Types