C# Reference Types Explained

Last Updated: Nov 09, 2025
3 min read
Legacy Archive
Legacy Guidance: This article preserves historical web development content. For modern .NET 8+ best practices, visit our Tutorials section.

In the C# unified type system, every type is derived from System.Object. This design means that both value types and reference types share a common base but behave differently. Reference types hold a reference to data stored on the managed heap and are set to null until you assign an object. Value types, on the other hand, store the actual data. Understanding how reference types work helps you manage memory and avoid common pitfalls like null reference exceptions.

A reference type variable doesn’t store the object itself; it stores the memory address where the object resides. When you create a new object with the new operator, the runtime allocates memory on the managed heap and returns a reference. That reference lives on the stack or inside another object, depending on where it was declared. Until you assign a reference, its value is null. Trying to access members on a null reference triggers an exception. Arrays, classes, records, delegates and interfaces are all reference types.

Classes

In C#, classes let you define data structures that encapsulate state and behaviour. Because classes are reference types, variables of a class type store a reference to an instance. The variable holds a reference to the object in the heap. Changes made through one reference are reflected in others pointing to the same instance.

Counter Class
// Define a simple class
public class Counter
{
    private int count;
    public int GetCount() => count;
    public void SetCount(int value) => count = value;
}
Using the Class
public static void Main()
{
    Counter counter = new Counter();
    counter.SetCount(10);
    int current = counter.GetCount();
    // current now holds 10
}

Strings

The string type in C# is an alias for the System.String class. Even though it feels like a primitive type, it’s actually a reference type and is immutable: once created, its contents can’t change. The String class is sealed, which means you can’t inherit from it. Common operations such as concatenation and comparison actually create new strings. C# provides many methods on String like Concat, Split, Trim, ToLower, and GetHashCode, giving you flexibility to work with text.

String Operations
public static void Main()
{
    string s1 = "Hello";
    string s2 = "World";
    bool equal = s1.Equals(s2); // false
    string loud = s1.ToUpper(); // "HELLO"
    string combined = string.Concat(s1, " ", s2); // "Hello World"
}

Interfaces

Interfaces define a contract of methods and properties without providing implementation. Classes can implement multiple interfaces, allowing you to describe capabilities without committing to a single inheritance chain. This is useful because C# classes support only single inheritance but can implement many interfaces.

Implementing Interfaces
public interface ILogger
{
    void Log(string message);
}

public interface ICounter
{
    int Increment();
}

public class LoggerCounter : ILogger, ICounter
{
    private int total;
    public void Log(string message) => Console.WriteLine(message);
    public int Increment() => ++total;
}

Delegates

Delegates are types that represent method signatures. They allow you to store references to methods and call them later. Delegates power event handlers and callback patterns in .NET. When you invoke a delegate, it calls the method passed to the delegate. This indirection allows you to change behaviour at runtime and compose multiple methods with multicast delegates.

Delegate Example
public delegate void Notify(string message);

public class Notifier
{
    public static void SayHi(string msg) => Console.WriteLine(msg);
    public static void Main()
    {
        Notify greet = new Notify(SayHi);
        greet("Hello from delegate!");
    }
}

Best Practices

When working with reference types, always initialise variables before use to avoid null reference exceptions. Use interfaces to decouple components and enable testing. Prefer using var when the type is obvious from the right-hand side. Remember that reference type variables on the stack point to objects on the heap. Use delegates and events to implement observer patterns rather than manual callback lists.

FAQ

What’s the difference between value and reference types?

Value types store data directly, while reference types hold a memory address pointing to data on the heap. A reference is null until you assign an object, and dereferencing a null value throws an exception.

Why is System.String sealed in C#?

The String class is sealed to prevent inheritance. This ensures predictable behaviour and allows the runtime to optimise string operations. You can still manipulate strings using methods like Concat, Split and Trim.

Can a class implement multiple interfaces?

Yes. A class can implement more than one interface, letting you define multiple sets of behaviour without inheritance. Each interface lists required members that the class must implement.

How do delegates relate to events?

Delegates are types that reference methods, and events use delegates under the hood. You register methods with an event through its delegate, and the event invokes each subscribed method when it’s raised.

Back to Articles