.Net application development specialists
asp.net, c#, vb.net, html, javascript, jquery, html, xhtml, css, oop, design patterns, sql server, mvc and much more
contact: admin@paxium.co.uk

Paxium is the company owned by myself, Dave Amour and used for providing IT contract development services including


  • Application development - Desktop, Web, Services - with Classic ASP, Asp.net WebForms, Asp.net MVC, Asp.net Core, .NET 8/9/10
  • Azure - Azure Functions, App Services, Azure SQL, Service Bus, Blob Storage, Key Vault, API Management (APIM), Logic Apps and Application Insights
  • Html, Css, JavaScript, jQuery, React, C#, SQL Server, Ado.net, Entity Framework, NHibernate, TDD, WebApi, GIT, IIS
  • Database schema design, implementation & ETL activities
  • Website design and hosting including email hosting
  • Training - typically one to one sessions
  • Reverse Engineering and documentation of undocumented systems
  • Code Reviews
  • Performance Tuning
  • Located in Cannock, Staffordshire
Rugeley Chess Club Buying Butler Cuckooland Katmaid Pet Sitting Services Roland Garros 60 60 Golf cement Technical Conformity Goofy MaggieBears Vacc Track Find Your Smart Phone eBate Taylors Poultry Services Lafarge Rebates System Codemasters Grid Game eBate DOFF
ENCODING TEST: ───────────────► ↓ ↑ ← →

C# Objects, References, Pass-by-Value, ref, and Garbage Collection

C# object references can seem confusing because several different things are happening at once: you can change an object through a method parameter, you cannot normally replace the caller's variable from that method, you can reassign a local variable directly, and the old object may later be reclaimed by the garbage collector.

The rule that explains almost everything is this:

C# passes parameters by value by default.

For a class, the value being copied is the reference to the object, not the object itself.

Start With a Simple Class

class Person
{
    public string Name { get; set; }
}

Now create an instance:

var a = new Person { Name = "Bob" };

Conceptually:

a ───────────────► Person object
                    Name = "Bob"

The variable a does not contain the whole Person. It contains a reference to that object.


Direct Reassignment Works Because You Are Changing the Actual Variable

Consider:

var a = new Person { Name = "Bob" };

Console.WriteLine(a.Name);

a = new Person { Name = "Sarah" };

Console.WriteLine(a.Name);

The output is:

Bob
Sarah

Why does this work? Because there is no method parameter involved. You are reassigning the actual variable a.

Initially:

a ───────────────► Person("Bob")

Then this runs:

a = new Person { Name = "Sarah" };

A new object is created and the variable a is changed to point at it:

a ───────────────► Person("Sarah")

                   Person("Bob")

There is no copied parameter here. You are assigning directly to a, so a itself changes.


What Happens to the Old Bob Object?

After a is changed to point to the new Sarah object, the old Bob object still exists for the moment, but a no longer points to it.

a ───────────────► Person("Sarah")

                   Person("Bob")   ← no longer referenced by a

If no other reachable variable or object refers to the Bob object, it becomes eligible for garbage collection.

Eligible for garbage collection does not mean "deleted immediately". It means the .NET garbage collector is now free to reclaim that object's memory when a collection occurs.

The lifecycle is roughly:

Create Person("Bob")
        ↓
a points to Bob
        ↓
Create Person("Sarah")
        ↓
a is reassigned to Sarah
        ↓
Bob has no reachable references
        ↓
Bob is eligible for garbage collection
        ↓
GC eventually reclaims Bob's memory

What If Another Reference Still Points to Bob?

var a = new Person { Name = "Bob" };
var b = a;

a = new Person { Name = "Sarah" };

Console.WriteLine(b.Name);

The output is still:

Bob

Before reassignment:

a ──────┐
         ▼
     Person("Bob")
         ▲
b ───────┘

After reassigning a:

a ───────────────► Person("Sarah")

b ───────────────► Person("Bob")

The Bob object is still reachable through b, so it is not eligible for garbage collection.


Now Compare This With Passing the Object to a Method

Suppose we do this:

static void Change(Person p)
{
    p.Name = "David";
}

static void Main()
{
    var a = new Person { Name = "Bob" };

    Change(a);

    Console.WriteLine(a.Name);
}

The output is:

David

This works because when Change(a) is called, the reference stored in a is copied into p.

Main a      ───────┐
                    │
                    ▼
                Person object
                Name = "Bob"
                    ▲
                    │
Change p    ────────┘

There are two separate variables, but both contain references to the same object.

The reference is copied. The object is not copied.

Why p.Name = "David" Changes the Original Object

static void Change(Person p)
{
    p.Name = "David";
}

The statement p.Name = "David" follows the reference stored in p and changes the object at the other end.

Since a and p point at the same object, both observe the change:

Main a      ───────┐
                    │
                    ▼
                Person object
                Name = "David"
                    ▲
                    │
Change p    ────────┘

Why Replacing the Parameter Does Not Change the Caller

Now compare:

static void Replace(Person p)
{
    p = new Person { Name = "Tiffany" };
}

When the method begins:

Main a      ───────┐
                    │
                    ▼
                Person("Bob")
                    ▲
                    │
Replace p   ────────┘

Then this line runs:

p = new Person { Name = "Tiffany" };

That line does not change the caller's variable. It changes only the local parameter variable p.

Main a      ─────────────► Person("Bob")

Replace p   ─────────────► Person("Tiffany")

The variable in Main still points at Bob. The method parameter now points at Tiffany.

When the method returns, the local variable p disappears.


After Reassigning the Parameter, You Have Lost the Original Through That Parameter

Consider:

static void Replace(Person p)
{
    p = new Person { Name = "Tiffany" };

    p.Name = "David";
}

Once p has been reassigned, it no longer points at the original object. So p.Name = "David" changes the new object.

Caller a    ─────────────► Person("Bob")

Method p    ─────────────► Person("David")

Unless you kept another reference to the original object, you can no longer access that original through p.

Keeping the Original Reference

static void Replace(Person p)
{
    var original = p;

    p = new Person { Name = "Tiffany" };

    original.Name = "David";
}

Now:

Caller a       ───────┐
                       ▼
                   Person("David")
                       ▲
original         ──────┘

Method p       ───────────► Person("Tiffany")

The caller sees "David" because original still points to the original object.


Why C# Behaves This Way

The behaviour follows one general design rule rather than a special rule for objects: ordinary parameters receive copies of values.

For an integer:

static void ChangeNumber(int x)
{
    x = 20;
}

int number = 10;
ChangeNumber(number);

Console.WriteLine(number);

The result is still:

10

The method got a copy of the integer value.

With a class, the exact same idea applies:

Person a = new Person();

The value stored in a is a reference. Passing a to a normal parameter copies that reference value.

A reference type is not the same thing as pass-by-reference. A class is a reference type, but a normal parameter such as Person p is still passed by value.


Why This Design Is Useful

Imagine normal object parameters were automatically passed by reference.

This innocent-looking call:

ProcessCustomer(customer);

could silently do:

static void ProcessCustomer(Person customer)
{
    customer = null;
}

and wipe out the caller's variable. Or:

static void ProcessCustomer(Person customer)
{
    customer = new Person { Name = "Someone Else" };
}

could silently replace the caller's reference.

C# avoids that by making ordinary parameters work on a copy of the reference. A method can still mutate the object through that reference, but it cannot normally replace the caller's variable.


Using ref When You Really Want to Replace the Caller's Variable

If replacing the caller's reference is intentional, use ref:

static void Replace(ref Person p)
{
    p = new Person { Name = "Tiffany" };
}

and call it like this:

Replace(ref a);

The ref keyword is required in both places. Now the method can update the caller's variable itself:

Replace parameter
        │
        ▼
caller variable a ─────────► Person object

So this:

p = new Person { Name = "Tiffany" };

changes what the caller's a points to.


Why ref Appears at the Call Site Too

C# requires this:

Replace(ref a);

rather than:

Replace(a);

because it makes the potentially important side effect visible to the caller.

Seeing ref a tells you immediately: "This method is allowed to change the actual variable a, not merely work with the object it currently references."


Direct Reassignment Versus Method Reassignment

Code What is being changed? Effect
a.Name = "David"; The object The object changes
a = new Person(); The actual local variable a a points to a new object
void M(Person p) { p.Name = "David"; } The shared object The caller sees the change
void M(Person p) { p = new Person(); } The copied parameter variable The caller's reference does not change
void M(ref Person p) { p = new Person(); } The caller's actual variable The caller's reference changes

A Useful Analogy

Think of an object as a house and a reference as the house address written on a piece of paper.

When you pass an object normally to a method, C# gives the method a photocopy of the address. Both pieces of paper lead to the same house.

If the method goes to the house and paints the door:

p.Name = "David";

everyone sees the changed house.

If the method erases the address on its photocopy and writes down a different address:

p = new Person();

your original piece of paper has not changed.

Using ref is like allowing the method to alter your original piece of paper.


Garbage Collection Fits Naturally Into This Model

Objects do not disappear just because one variable stops pointing at them. They become collectable only when they are no longer reachable through any live reference.

Example:

var a = new Person { Name = "Bob" };

a = new Person { Name = "Sarah" };

If nothing else refers to Bob:

a ─────────────► Person("Sarah")

                Person("Bob")
                no reachable references
                ↓
                eligible for GC

But:

var a = new Person { Name = "Bob" };
var b = a;

a = new Person { Name = "Sarah" };

leaves Bob alive:

a ─────────────► Person("Sarah")

b ─────────────► Person("Bob")

because b still provides a reachable reference.

Does Garbage Collection Happen Immediately?

No.

The .NET runtime decides when to perform garbage collection based on memory pressure and other runtime conditions. An unreachable object may remain in memory for some time before its storage is reclaimed.

You should generally not write normal application logic that depends on knowing exactly when garbage collection occurs.

What About the String "Bob"?

The old Person object can become eligible for collection, but string literals such as "Bob" are normally interned by .NET. That means the literal itself is typically retained through the runtime's string intern mechanism even after the old Person object is no longer reachable.


Returning a New Object Is Often Clearer Than ref

If a method's job is to create a replacement object, returning it is often clearer:

static Person Replace(Person p)
{
    return new Person { Name = "Tiffany" };
}

var a = new Person { Name = "Bob" };

a = Replace(a);

At the call site, it is obvious that a is being reassigned.


Complete Demonstration

using System;

public class Program
{
    class Person
    {
        public string Name { get; set; }
    }

    static void Change(Person p)
    {
        p.Name = "David";
    }

    static void Replace(Person p)
    {
        p = new Person { Name = "Tiffany" };
    }

    static void ReplaceWithRef(ref Person p)
    {
        p = new Person { Name = "Sarah" };
    }

    static void Main()
    {
        var a = new Person { Name = "Bob" };

        Console.WriteLine(a.Name);   // Bob

        a = new Person { Name = "Tim" };

        Console.WriteLine(a.Name);   // Tim

        Change(a);

        Console.WriteLine(a.Name);   // David

        Replace(a);

        Console.WriteLine(a.Name);   // David

        ReplaceWithRef(ref a);

        Console.WriteLine(a.Name);   // Sarah
    }
}

Final Mental Model

  1. A variable of a class type contains a reference to an object.
  2. Assigning a new object directly to that variable changes the variable itself.
  3. Passing the variable to a normal method copies the reference.
  4. The copied reference still reaches the same object, so object properties can be changed.
  5. Reassigning the copied parameter only changes the local parameter.
  6. Using ref lets the method change the caller's actual variable.
  7. When an object has no reachable references, it becomes eligible for garbage collection.
  8. Garbage collection happens later, when the .NET runtime decides it is appropriate.
a.Name = "David"          changes the object.
a = new Person()          changes the actual local variable.
p = new Person()          inside a normal method changes only the copied parameter.
ref Person p               allows the method to change the caller's variable.
An old object with no reachable references becomes eligible for garbage collection.