Mutable Shared State
Before you begin: Objects, lists, references, and assignment.
Mutable shared state is data that more than one part of a program can change. Two variables may refer to the same list or object, so a change through one variable is visible through the other. This can surprise even a single-threaded program; threads introduce additional risks. 1
Suppose a party roster receives a list of player names. Does the party keep that exact list, or keep the names as they were when the party was created? Those are different promises.
Original version: both owners have the same list
public sealed class Party
{
public List<string> Names { get; }
public Party(List<string> names)
{
Names = names;
}
}List<string> names = new List<string> { "Mira", "Sol" };
Party party = new Party(names);
names.Clear();
Console.WriteLine(party.Names.Count); // 0Names = names copies a reference, not the list contents. Clearing names therefore empties the party’s roster. A get-only property prevents assigning a different list through that property; it does not make the existing list immutable.
class Party:
def __init__(self, names):
self.names = namesnames = ["Mira", "Sol"]
party = Party(names)
names.clear()
print(len(party.names)) # 0self.names = names stores a reference to the existing list. It does not copy the contents. Clearing names therefore empties the party’s roster too.
Revised version: keep an independent roster
For this example, the intended rule is that constructing a party captures its initial names. Later edits to the input list must not change that roster.
public sealed class Party
{
private readonly string[] names;
public Party(IEnumerable<string> names)
{
this.names = names.ToArray();
}
public string[] Names
{
get { return (string[])names.Clone(); }
}
}List<string> names = new List<string> { "Mira", "Sol" };
Party party = new Party(names);
names.Clear();
Console.WriteLine(party.Names.Length); // 2The constructor copies the names into an array. The property also returns a copy, so callers cannot edit the stored array. Both directions matter: copying on input alone would still leave a problem if the property exposed the internal array.
class Party:
def __init__(self, names):
self._names = tuple(names)
@property
def names(self):
return self._namesnames = ["Mira", "Sol"]
party = Party(names)
names.clear()
print(len(party.names)) # 2The constructor captures the names in a tuple. Callers cannot append to that tuple or replace its elements. Returning it does not expose a mutable list. Deliberately reassigning the private-by-convention _names attribute remains possible in Python.
Copies are not a universal solution
These are strings, which cannot themselves be mutated. If the collection held mutable player objects, copying the collection would still share the player objects. That is a shallow copy, not an independent copy of an entire object graph.
The revision also intentionally changes the sharing behavior. If callers depend on a live roster, use controlled add/remove operations or an explicitly shared model rather than silently substituting a snapshot.
For concurrent access, assigning a clear owner, using immutable messages, or protecting a complete operation with synchronization may be appropriate. A copy does not make unrelated shared state thread-safe. First decide who owns each change; then choose a mechanism that enforces that decision.
Sources
- Python — Assignment and copies, explaining shared references and shallow versus deep copying.
- Microsoft — Reference types, explaining multiple references to the same object.