CodeGym /Courses /C# SELF /Immutability and with...

Immutability and with-expressions

C# SELF
Level 19 , Lesson 2
Available

1. What is immutability?

Let's start with an analogy: imagine you're an accountant who makes a sales report every day. Like a real pro, you don't change last week's report, you make a new one—based on the old one, but with updated data. It's the same with immutable objects in programming: after you create such an object, it never changes, and any "change" means making a new copy.

Immutability (immutability) is a property of an object to not change after initialization. All its properties become "frozen": if you want a different value—make a new object.

Why do we need this?

  • It keeps things safe: if nobody can accidentally change your object, your data won't suddenly get messed up. This happens a lot in multithreaded programs, when several threads try to change stuff at the same time.
  • Makes debugging easier: if the object doesn't change, you know exactly what happened after it was created.
  • Super handy for passing data around, especially in distributed systems where copies can drift apart.
  • Lets you make "snapshots" (snapshots)—the history of changes becomes obvious.

2. Immutability in record types

When you declare a regular class, its properties are mutable (mutable) by default. Example:

public class UserProfile
{
    public string Name { get; set; }
    public int Age { get; set; }
}

var user = new UserProfile { Name = "Ivan", Age = 25 };
user.Age = 26; // All good—regular class: changing age "on the fly"

With record it's different by default: they're for storing immutable data.


public record UserProfile(string Name, int Age);

// Creating an object:
var user = new UserProfile("Ivan", 25);

// Trying to change age:
user.Age = 26; // Compilation error: property is read-only!
record — properties are read-only (init-only)

Properties of a positional record are declared read-only (init-only). You can't change them after creation, but you can use a with-expression to make a new copy with a changed property.

Mutable classes vs immutable records

Class Record-positional
Properties by default
get; set;
Immutable
get; init;
How to change
Access the property
Only by creating a new copy
Object comparison
By reference (ReferenceEquals)
By value (Equals)
Handy for passing data
Not always
Yep

3. with-expressions

You might be thinking—"record is cool, but how do I live if I can't change them?" That's where the magic of with-expressions comes in!

with is a special syntax that lets you create a new copy of a record, changing only the properties you want.
So: "Take this object, make a copy, but tweak a couple properties here."

Simple example


var user1 = new UserProfile("Anna", 30);
// ... but life moves on, and Anna got older
var user2 = user1 with { Age = 31 };
// user1 stayed the same, user2 is a copy but a year older

Console.WriteLine(user1); // UserProfile { Name = Anna, Age = 30 }
Console.WriteLine(user2); // UserProfile { Name = Anna, Age = 31 }

Under the hood

It's not a mutant clone, it's a new object created with a special auto-generated Clone() method that makes a copy and plugs in new values.

If with-expressions existed in real life, you could wake up in the morning not in your "old tired body", but in a copy of yourself with a tuned-up mood and bigger muscles (but only if you were a record).

4. A bit about nesting and copying

If a record contains other records—you're good:

public record Address(string City, string Street);
public record Student(string Name, int Age, string Email, Address Home);

var a1 = new Address("Moskva", "Tverskaya");
var s1 = new Student("Lena", 21, "lena@mail.ru", a1);

var s2 = s1 with { Home = a1 with { Street = "Arbat" } };

Here everything will work truly immutably, because the nested Address is also a record.

5. Last gotchas

Positional records = compactness

You can declare a record in the “short” form (positional syntax). Then all properties automatically get init-only.

public record Course(string Name, int Credits);

var c1 = new Course("C#", 5);
var c2 = c1 with { Credits = 6 };

Analogy with read-only (init-only) properties

In a record, you can explicitly declare properties like this:

public record Student
{
    public string Name { get; init; }
    public int Age { get; init; }
}

You can only change these properties during initialization (or with with).

6. Practice: demo app

Let's write our training "Online School". Let's say we already have a record for a student:

public record Student(string Name, int Age, string Email);

Classic: someone made a typo in the address, but the student already created an account. How do you "update" the email? Of course, with with!


var student = new Student("Ekaterina", 19, "kate@school.com");
var updatedStudent = student with { Email = "ekaterina@school.com" };

// Let's check the objects:
Console.WriteLine(student);       // Student { Name = Ekaterina, Age = 19, Email = kate@school.com }
Console.WriteLine(updatedStudent); // Student { Name = Ekaterina, Age = 19, Email = ekaterina@school.com }

7. Typical mistakes and pitfalls

Now a bit about the pain—where students most often mess up when playing with immutable records.

  • First, a lot of folks think with changes the original object. Actually, the original object stays the same, and a new one is created with the changed fields. Sometimes this can trip you up, losing the new values.
  • Second, remember: if your record has nested mutable objects (like an array or List), the with-expression does NOT do a deep copy! Your collection will be the same for both copies.

public record Student(string Name, int Age, List<string> Subjects);

var s1 = new Student("Oleg", 22, new List<string> { "Math", "Physics" });
var s2 = s1 with { };

s1.Subjects.Add("C#"); // Oops, now s2.Subjects also includes "C#"

That's why for truly immutable state it's better to use only simple types or collections that are themselves immutable (ImmutableList<T> and others from System.Collections.Immutable).

If you want to guarantee real immutability, use these collections or do manual deep copying.

2
Task
C# SELF, level 19, lesson 2
Locked
Creating a simple record and using the with-expression
Creating a simple record and using the with-expression
2
Task
C# SELF, level 19, lesson 2
Locked
Nested record and updating a nested object
Nested record and updating a nested object
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION