Immutability Patterns
Part of the Object Oriented Programming section of Coddy's C# journey — lesson 34 of 70.
Immutability takes encapsulation to its logical extreme - once an object is created, it can never change. This eliminates entire categories of bugs because you never have to worry about something unexpectedly modifying your data.
The simplest way to create an immutable class is to use readonly fields with a constructor that sets all values, and provide no setters:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}Once a Point is created, its coordinates cannot change. But what if you need a modified version? Instead of changing the existing object, you return a new one:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}The original point stays unchanged while you get a new point with updated values. This pattern is used throughout .NET - strings, for example, are immutable. When you call ToUpper(), you get a new string rather than modifying the original.
Immutable objects are inherently thread-safe since no code can modify them after creation. They're also easier to reason about because you know their state will never change unexpectedly.
Challenge
EasyLet's build an immutable money system that demonstrates how objects can be designed to never change after creation. You'll create a Money class where every operation returns a new object instead of modifying the original - a pattern that makes your code safer and easier to reason about.
You'll organize your code across two files:
Money.cs: Create an immutableMoneyclass in theFinancenamespace. Your money should have:- Two
readonlyfields:Amount(decimal) andCurrency(string) - A constructor that sets both values - once created, a Money object can never change
- A method
Add(decimal value)that returns a newMoneyobject with the increased amount (same currency) - A method
Subtract(decimal value)that returns a newMoneyobject with the decreased amount (same currency) - A method
Convert(string newCurrency, decimal rate)that returns a newMoneyobject with the amount multiplied by the rate and the new currency - A method
GetDisplay()that returns a string in the format"{Amount} {Currency}"
Moneyinstances!- Two
Program.cs: In your main file, demonstrate immutability in action. Create an initialMoneyobject, then perform operations on it while keeping references to both the original and the results. This will prove that the original never changes even after multiple operations.
You will receive four inputs:
- Initial amount (decimal)
- Initial currency (string)
- Amount to add (decimal)
- Conversion rate to EUR (decimal)
Print the output in this format:
Original: {GetDisplay()}
After add: {GetDisplay()}
Original unchanged: {GetDisplay()}
Converted: {GetDisplay()}For example, if the inputs are 100.00, USD, 50.00, and 0.85, the output should be:
Original: 100.00 USD
After add: 150.00 USD
Original unchanged: 100.00 USD
Converted: 127.50 EURNotice how the original Money object still shows 100.00 USD even after calling Add() - that's immutability! The Add() method created a completely new object with 150.00 USD, leaving the original untouched. The conversion is performed on the result of the add operation.
Cheat sheet
Immutability means that once an object is created, it can never change. This eliminates bugs related to unexpected data modifications.
Create an immutable class using readonly fields with a constructor and no setters:
public class Point
{
public readonly int X;
public readonly int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}To modify an immutable object, return a new instance instead of changing the existing one:
public Point Move(int deltaX, int deltaY)
{
return new Point(X + deltaX, Y + deltaY);
}Benefits of immutable objects:
- Thread-safe by design since no code can modify them after creation
- Easier to reason about because their state never changes unexpectedly
- Used throughout .NET (e.g., strings are immutable)
Try it yourself
using System;
using Finance;
class Program
{
public static void Main(string[] args)
{
// Read inputs
decimal initialAmount = Convert.ToDecimal(Console.ReadLine());
string initialCurrency = Console.ReadLine();
decimal amountToAdd = Convert.ToDecimal(Console.ReadLine());
decimal conversionRate = Convert.ToDecimal(Console.ReadLine());
// TODO: Create an initial Money object with the initial amount and currency
// TODO: Print the original money using GetDisplay()
// TODO: Call Add() on the original and store the result in a new variable
// TODO: Print the result after add using GetDisplay()
// TODO: Print the original again to prove it's unchanged
// TODO: Convert the added result to EUR using the conversion rate
// TODO: Print the converted money using GetDisplay()
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator3Class Architecture
Instance vs Static Data'readonly' & 'const' KeywordsBacking FieldsRecap - Bank Account Manager6Encapsulation
Access ModifiersProperties for EncapsulationData Hiding ImplementationImmutability PatternsRecap - Student Records