Change Reference to Value
Before you begin: Objects, references, immutability, and equality.
Change Reference to Value treats an object as a value described by its contents rather than as a unique, shared identity. Updates create replacement values instead of changing an object that other code may still hold. Equality should compare the relevant contents. 1
A useful question is: do we care which particular object this is, or what information it contains?
Original version: editing an address changes an older reference
A delivery address is simplified to one street string. A saved reference and a current reference both point at one mutable address.
public sealed class Address
{
public string Street { get; set; }
public Address(string street) { Street = street; }
}Address current = new Address("Dock 4");
Address saved = current;
current.Street = "Dock 9";
Console.WriteLine(saved.Street); // Dock 9Assigning saved = current did not preserve the old street. Both variables still refer to the object whose property was changed.
class Address:
def __init__(self, street):
self.street = streetcurrent = Address("Dock 4")
saved = current
current.street = "Dock 9"
print(saved.street) # Dock 9The assignment copied a reference. It did not create an independent saved address.
Revised version: replace the value
public sealed record Address
{
public string Street { get; }
public Address(string street) { Street = street; }
}Address current = new Address("Dock 4");
Address saved = current;
current = new Address("Dock 9");
Console.WriteLine(saved.Street); // Dock 4
Console.WriteLine(saved == new Address("Dock 4")); // TrueThe get-only string cannot be changed through the public API. Assigning a new address to current leaves saved unchanged. A C# record supplies value equality, so separately constructed addresses with the same street compare equal. This is a semantic choice, not merely shorter class syntax. 2
from dataclasses import dataclass
@dataclass(frozen=True)
class Address:
street: strcurrent = Address("Dock 4")
saved = current
current = Address("Dock 9")
print(saved.street) # Dock 4
print(saved == Address("Dock 4")) # TrueThe frozen dataclass prevents ordinary field assignment and compares instances by their fields. The street itself is an immutable string. A frozen dataclass containing a mutable list would not make that list immutable. 3
Preserve values where history matters
The revision intentionally stops updates from propagating through old references. That is useful for snapshots, coordinates, amounts, and other values without independent identity. It is not automatically correct for a live customer record that several views must edit together.
Define all fields that contribute to equality, remove or replace mutating operations, and inspect callers that use reference identity. Persisted records and caches may also rely on identity in ways the class declaration does not reveal.
Both revised examples still use reference-based objects at the language-runtime level. The refactoring concerns their application semantics: immutable contents and value equality, not necessarily stack allocation or C# value types.
Sources
- Martin Fowler — Change Reference to Value.
- Microsoft — Records, including value equality and immutability limitations.
- Python — dataclasses.