Modern C# · Lesson 9

Value copies and references

Predict whether assignment copies data or shares an object.

Assignment copies the value held by a variable

Every assignment in this sample copies a variable value, but the copied value has a different meaning for the two declared types. CounterValue is a struct whose integer field is part of the value being copied. CounterObject is a class, so its variable holds an object reference. Copying that reference gives another route to the same object. Value versus reference semantics explains the behavior without relying on an oversimplified rule about where memory must physically be allocated.

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.

Why the output is two and eight

The struct variable begins with Count two. Assigning it to copy creates a separate struct value, and changing copy.Count to eight leaves value.Count equal to two. The class variable also begins with Count two, but alias receives the same object reference. Changing alias.Count updates the object that reference also reaches, so printing reference.Count produces eight. The expected two-line result is therefore 2 and 8; neither line reports the changed struct copy directly.

var copy = value;

A field change is different from reassignment

Writing a class field through alias changes the shared object’s state. Reassigning alias to a newly constructed object would instead change only which reference alias stores; it would not make reference point to that new object. The distinction is central to reasoning about methods and collections that receive object references. Ask whether an operation changes the referred object or replaces one local reference variable. Both can look like an assignment in source code, yet they affect different parts of the program’s state.

A copied struct can still contain shared objects

The example’s struct contains only an integer, so independent field mutation is easy to see. If a struct contains a List reference, copying the struct copies that reference too. The two struct values can then reach the same mutable list even while their own scalar fields remain independent. A value copy is not automatically a deep clone. Similarly, choosing a class does not require all its objects to be mutable; immutable classes can provide predictable shared values with a different assignment model.

Practice: separate mutation from rebinding

First print copy.Count as a third observation and predict eight. Then replace the class field mutation with assigning alias to a new CounterObject whose Count is eight. Predict that reference.Count now remains two because only the alias variable changed its target. Keep these as separate experiments rather than applying both edits together. A small state diagram with variables and object arrows makes the difference visible and gives you a concrete explanation for each output line.

  • Track each variable separately and label whether it contains a struct value or an object reference.
  • Do not describe ReferenceEquals as a test of whether two structs contain equal field values.

Practice: move the change behind a method boundary

As an independent change, write one helper accepting CounterValue and another accepting CounterObject, each assigning Count eight through its parameter. Call both helpers with fresh values and inspect the originals afterward. Ordinary by-value parameter passing copies the struct value or the object reference respectively. Then add an explicit ref parameter to a separate struct helper and explain why its ability to modify the caller’s variable is a different contract, rather than evidence that structs sometimes copy unpredictably.

  • Use fresh instances for each helper call and write expected original values before running.
  • For a nested-list extension, test scalar field independence and shared list mutation as separate facts.

Explain identity, storage ownership, and mutation independently

The most useful outcome of this lesson is a vocabulary for following state. A variable can be copied, an object can be shared, a field can be changed, and a variable can be rebound. Those are different events. When a larger feature behaves unexpectedly, identify which event actually occurred before selecting a fix such as copying a collection or restricting mutation. This reasoning applies equally to local .NET code in SharpRune; it does not depend on desktop-only APIs, a particular processor layout, or an imagined universal stack-and-heap rule.

  • Baseline prints the untouched struct original as 2 and the shared class object’s Count as 8.
  • A plain class-variable assignment copies a reference; it does not clone the referenced object.
  • Nested references require their own ownership policy even when the containing type is a struct.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

var value = new CounterValue { Count = 2 };
var copy = value;
copy.Count = 8;
var reference = new CounterObject { Count = 2 };
var alias = reference;
alias.Count = 8;
Console.WriteLine(value.Count);
Console.WriteLine(reference.Count);
struct CounterValue { public int Count; }
class CounterObject { public int Count; }

Expected output

2
8
Try this
A copy of a reference still points at the same object.

Further reading