Object-Oriented C# · Lesson 1

Values and references

A value copy keeps its own bits. A reference copy names the same object.

Assignment copies a value, but the value may be a reference

The useful distinction is what each variable stores and what an assignment copies. An int variable stores an integer value. A variable of a class type stores a reference to an object. Assigning either variable copies its current value, but copying an integer gives another independent integer slot, while copying an object reference lets two slots refer to the same object. There is no special ref parameter or variable-alias declaration in this example; both assignments use ordinary assignment syntax.

Avoid reducing this distinction to a slogan that values live on the stack and references live on the heap. Storage depends on context, including fields, arrays, captured variables, and boxing. The behavior that matters here is independent of that implementation picture: after copying the integer, changing the second variable does not change the first; after copying the reference, changing the shared object's field is visible through either reference. A diagram should therefore show variable slots and the object they identify, not invent separate Ticket objects for every class variable.

Two panels compare independent integer copies with two references to one mutable list.View full size ↗
Assignment copies a value. For a reference type, that copied value identifies the same object.
Read the diagram

For integers: int a = 7; int b = a; b = 9. The final values are a = 7 and b = 9. Updating b does not change a.

For lists: var a = new List<int> { 2, 4 }; var b = a; b.Add(6). Both variables refer to the same list, which now contains 2, 4, 6. Reassigning b to another list would change b's reference, rather than mutate a's list.

This diagram is a separate conceptual example. The runnable lesson below uses its own values. A value type can itself contain references; copying such a value does not recursively clone the referenced objects.

Illustration created with ImageGen for SharpRune.

Trace the independent number and the shared ticket separately

The first declaration stores twelve in amount. Assigning amount to copy stores twelve in a second integer variable. Setting copy to four replaces only that second variable's integer value, so printing amount still gives 12. The program then constructs one Ticket object and explicitly sets its Count field to twelve. That field belongs to the object, not to the variable ticket. The original integer variables and this Ticket field are separate pieces of state.

Assigning ticket to same copies the reference to that one Ticket object. There is no second new expression and no clone. same.Count = 4 follows the copied reference and changes the shared object's field. Reading ticket.Count follows the other reference to the same object and therefore prints 4. The unchanged output has two lines, twelve and four. Neither the name same nor its declaration makes it permanently track the variable ticket; they are independent reference slots that currently identify one shared object.

var same = ticket;
same.Count = 4;
Console.WriteLine(ticket.Count);

Reassigning a reference and mutating an object are different actions

If you assign a newly constructed Ticket to same, you replace same's reference rather than editing ticket's reference. The original object can remain available through ticket. Conversely, changing Count through same while the references are shared mutates that original object. A shallow collection or object copy may reproduce references and preserve this sharing at deeper levels. Do not assume the word copy means a complete independent object graph, and do not assume every class has value equality merely because an earlier lesson used records.

Create independent objects, then separate the references later

First replace the reference-copy statement with construction of another Ticket whose Count starts at twelve. Set that new object's Count to four and print both counts. Predict twelve for the original and four for the new object. Hint: count the new expressions and draw one object per construction. The assignments to fields then show which object changes; variable names alone do not establish object identity or independence.

Next restore the original sharing and its mutation to four. Afterward assign a new Ticket with Count nine to same and print ticket.Count and same.Count. Predict four followed by nine. Hint: redraw only the same variable's arrow at the reassignment; do not erase the original object still referenced by ticket. Finally use ReferenceEquals in an experiment to check identity before and after reassignment, and explain why that question differs from comparing two classes' domain contents.

Explain exactly which slot or object changes

For every assignment in this sample, identify whether it replaces a local integer, replaces a local reference, or mutates an object field. Then explain the original two-line result without relying on where a runtime allocates storage. This model scales to arrays, lists, method arguments, and snapshots: the key questions are which values were copied, which references remain shared, and which operation is permitted to change the state that other code can observe.

  • The number copy changes to four while amount stays twelve.
  • Only one Ticket is constructed in the original sample.
  • Both class variables initially refer to that Ticket.
  • The original output is 12, then 4.

A working example

var amount = 12;
var copy = amount;
copy = 4;
Console.WriteLine(amount);

var ticket = new Ticket();
ticket.Count = 12;
var same = ticket;
same.Count = 4;
Console.WriteLine(ticket.Count);

class Ticket
{
    public int Count;
}

Try this
Copy the number when the two names should stay independent.

Further reading