Change Value to Reference
Before you begin: Objects, references, dictionaries, and identity.
Change Value to Reference makes objects representing the same identity share one authoritative instance instead of carrying independent copies. A customer with identifier 7 should not become two different current customers merely because that customer bought two tickets. 1
Here, value and reference describe how the application treats identity. This is not an instruction to replace a C# struct with a class; every customer below is already a class instance.
Original version: two copies of the same customer
public sealed class Customer
{
public int Id { get; }
public string Name { get; set; }
public Customer(int id, string name) { Id = id; Name = name; }
}
public sealed class Ticket
{
public Customer Customer { get; }
public Ticket(Customer customer) { Customer = customer; }
}var first = new Ticket(new Customer(7, "Mira"));
var second = new Ticket(new Customer(7, "Mira"));
first.Customer.Name = "Mira Sol";
Console.WriteLine(second.Customer.Name); // MiraBoth instances say their identifier is 7, but they are separate mutable objects. Correcting the first name does not correct the second.
class Customer:
def __init__(self, customer_id, name):
self.id = customer_id
self.name = name
class Ticket:
def __init__(self, customer):
self.customer = customerfirst = Ticket(Customer(7, "Mira"))
second = Ticket(Customer(7, "Mira"))
first.customer.name = "Mira Sol"
print(second.customer.name) # MiraMatching identifiers do not automatically make the two objects share changes. They remain independent instances.
Revised version: obtain the existing customer
A small directory owns one customer instance per identifier. Tickets ask that directory for the customer instead of constructing another copy.
public sealed class Customer
{
public int Id { get; }
public string Name { get; set; }
public Customer(int id, string name) { Id = id; Name = name; }
}
public sealed class Customers
{
private readonly Dictionary<int, Customer> entries =
new Dictionary<int, Customer> { { 7, new Customer(7, "Mira") } };
public Customer Get(int id) { return entries[id]; }
}
public sealed class Ticket
{
public Customer Customer { get; }
public Ticket(Customer customer) { Customer = customer; }
}var customers = new Customers();
var first = new Ticket(customers.Get(7));
var second = new Ticket(customers.Get(7));
first.Customer.Name = "Mira Sol";
Console.WriteLine(second.Customer.Name); // Mira SolThe two ticket properties point to the same object. There is now one in-memory name to update within this directory’s lifetime.
class Customer:
def __init__(self, customer_id, name):
self.id = customer_id
self.name = name
class Customers:
def __init__(self):
self._entries = {7: Customer(7, "Mira")}
def get(self, customer_id):
return self._entries[customer_id]
class Ticket:
def __init__(self, customer):
self.customer = customercustomers = Customers()
first = Ticket(customers.get(7))
second = Ticket(customers.get(7))
first.customer.name = "Mira Sol"
print(second.customer.name) # Mira SolBoth lookups return the same instance. Changing its name is visible through either ticket.
Decide whether sharing is actually the requirement
A current customer record often has identity. A historical ticket’s printed passenger name may instead need to remain a snapshot. Sharing the current record would be wrong for that second requirement.
The example intentionally changes how later edits propagate. Before making this move in real code, identify which consumers require a live identity and which require a saved value. Reconcile conflicting copies and choose who may update the authoritative instance.
This directory is local, not a global singleton or a distributed consistency solution. Another process can still hold a different copy. Database transactions, cache invalidation, concurrent access, and object lifetime require their own decisions. Sharing removes one kind of duplication while introducing a responsibility to manage shared state deliberately.