C# Value Types and Reference Types

In this blog I will discuss what value and reference types in C# are and how we pass them as value and reference.
In C# -
  • Value Types - all numeric data type, boolean, char, datetime, structure (even if their member is ref type) and enum.
  • Ref Types - String, arrays (even if their elements are value types), class and delegates
Object data type is special. It can hold both ref type value or value type value. Object variable always holds a pointer to the data and never the data itself. However, if we assign a value type to an Object variable, it behaves as if it holds its own data.

Passing Parameters
  • Passing by val - when we pass by value a copy is passed and the original value is not changed.
    • Passing value type as value - any change that take place inside the method have no effect on the original value since a copy is passed.
    • Passing ref type as value - if we pass a class instance, since its ref type, changing property values will affect the original source also. However, the attempt to reassign the instance to a different memory location (like doing a new) only works inside the method and does not affect the original instance.
  • Passing by ref - by using ref or out keyword. Enables us to change the value in a passed environment and persist those changes in the calling environment.
    • Passing value type as ref - any change that take place inside the method is reflected on the original value as well.
    • Passing ref type as ref - if we pass a class instance, since its ref type, changing property values will affect the original source also. Unlike passing ref type as value, attempt to reassign the instance to a different memory location (like doing new) works inside the method and also affects the original instance.
String - string are reference types but their behavior is slightly different.
  • strings are immutable - that means that contents of a string object can't be modified once created. When we do that the compiler actually creates a new string object to hold the new string and that new object gets assigned to the original variable.
  • string passed as value - altering the string in the called method alters locally since its created new and the original string is not changed.
  • string passed as ref - altering the string in the called method alters the original string as well.
The code is provided below:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Passing value type as value - a copy is passed");
            int valByVal = 50;
            Console.WriteLine("Value before passing : " + valByVal);
            func1(50);
            Console.WriteLine("Value after passing outside called method : " + valByVal);

            Console.WriteLine();
            Person p = new Person { RefProperty = "abc", ValueProperty = 200 };
            Console.WriteLine("Passing ref type as value");
            Console.WriteLine("Value before passing : RefProperty = " + p.RefProperty + ", ValProperty = " + p.ValueProperty);
            func2(p);
            Console.WriteLine("Value after passing outside called method : RefProperty = " + p.RefProperty + ", ValProperty = " + p.ValueProperty);

            Console.WriteLine();
            int valByRef = 100;
            Console.WriteLine("Passing value type as ref - a pointer is passed ");
            Console.WriteLine("Value before passing : " + valByRef);
            func3(ref valByRef);
            Console.WriteLine("Value after passing outside called method : " + valByRef);

            Console.WriteLine();
            Person p2 = new Person { RefProperty = "abc", ValueProperty = 300 };
            Console.WriteLine("Passing ref type as ref");
            Console.WriteLine("Value before passing : RefProperty = " + p2.RefProperty + ", ValProperty = " + p2.ValueProperty);
            func4(ref p2);
            Console.WriteLine("Value after passing outside called method : RefProperty = " + p2.RefProperty + ", ValProperty = " + p2.ValueProperty);

            Console.WriteLine();
            Console.WriteLine("String behavior - passing by value");
            string str = "before";
            Console.WriteLine("before passing : " + str);
            func5(str);
            Console.WriteLine("after passing outside called method : " + str);

            Console.WriteLine();
            Console.WriteLine("String behavior - passing by ref");
            string str1 = "before";
            Console.WriteLine("before passing : " + str1);
            func6(ref str1);
            Console.WriteLine("after passing outside called method : " + str1);

            Console.WriteLine();
            Console.WriteLine("Stringbuilder behavior - passing by value");
            StringBuilder strb = new StringBuilder("before");
            Console.WriteLine("before passing : " + strb);
            func7(strb);
            Console.WriteLine("after passing outside called method : " + strb);

            Console.WriteLine();
            Console.WriteLine("Stringbuilder behavior - passing by ref");
            StringBuilder strb1 = new StringBuilder("before");
            Console.WriteLine("before passing : " + strb1);
            func8(ref strb1);
            Console.WriteLine("after passing outside called method : " + strb1);

            Console.ReadLine();
        }

        static void func1(int x)
        {
            x = x + 10;
            Console.WriteLine("Value after passing inside called method : " + x);
        }

        static void func2(Person person)
        {
            person.ValueProperty = person.ValueProperty + 10;
            person.RefProperty = "cde";
            Console.WriteLine("Value after passing inside called method : RefProperty = " + person.RefProperty + ", ValProperty = " + person.ValueProperty);
            // The change below remains local
            person = new Person { ValueProperty = 500, RefProperty = "xyz" };
            Console.WriteLine("Value after passing inside called method after trying to reinitialize : RefProperty = " + person.RefProperty + ", ValProperty = " + person.ValueProperty);
        }

        static void func3(ref int x)
        {
            x = x + 10;
            Console.WriteLine("Value after passing inside called method : " + x);
        }

        static void func4(ref Person person)
        {
            person.ValueProperty = person.ValueProperty + 10;
            person.RefProperty = "cde";
            Console.WriteLine("Value after passing inside called method : RefProperty = " + person.RefProperty + ", ValProperty = " + person.ValueProperty);
            // since this is by ref, this changes the original person too
            person = new Person { ValueProperty = 500, RefProperty = "xyz" };
            Console.WriteLine("Value after passing inside called method : RefProperty = " + person.RefProperty + ", ValProperty = " + person.ValueProperty);
        }

        static void func5(string s)
        {
            // new string gets created here and it gets assigned locally
            s = "after";
            Console.WriteLine("after passing inside called method : " + s);
        }

        static void func6(ref string s)
        {
            s = "after";
            Console.WriteLine("after passing inside called method : " + s);
        }

        static void func7(StringBuilder s)
        {
            s = new StringBuilder("after");
            Console.WriteLine("after passing inside called method : " + s);
        }

        static void func8(ref StringBuilder s)
        {
            s = new StringBuilder("after");
            Console.WriteLine("after passing inside called method : " + s);
        }

    }

    public class Person
    {
        public string RefProperty { get; set; }
        public int ValueProperty { get; set; }
    }

}

The output is below: