Value types, reference types, and null
What actually gets copied when you pass an int against a List, and how a value type opts into null.
After this lesson you can
- Say what gets copied when a value type is passed to a method
- Explain why two reference-type variables can point at the same object
- Use a nullable value type instead of a sentinel value
structs and the built-in numeric types are value types — a
variable holds the data itself. Classes are reference types — a
variable holds a pointer to data that lives elsewhere.
int a = 5;
int b = a; // b is a genuine copy
b = 10;
// a is still 5 — the two ints never shared anything
var list1 = new List<int> { 1, 2, 3 };
var list2 = list1; // list2 points at the same List object
list2.Add(4);
// list1 now has 4 elements too — there was only ever one list
Passing either kind to a method follows the same rule: a value type is copied in, so changes inside the method do not escape; a reference type passes the reference, so mutating the object it points to is visible to the caller. Reassigning the parameter itself to a new object, though, never escapes either way — only mutating what it already points to does.
Try it
public static class Solution{ public static string Describe() { int a = 5; int b = a; b = 10; var list1 = new List<int> { 1, 2, 3 }; var list2 = list1; list2.Add(4); return $"a={a} list1.Count={list1.Count}"; }}Structs are value types too
public struct Point
{
public int X, Y;
}
var p1 = new Point { X = 1, Y = 2 };
var p2 = p1; // a full copy, not a shared reference
p2.X = 99;
// p1.X is still 1
This is the actual reason to reach for a struct instead of a class:
small, immutable-in-spirit data where copy semantics are what you want,
and where the type is small enough that copying it is cheap. Reach for
class by default; struct is the deliberate exception.
Nullable value types
A plain value type can never be null — int x = null; does not
compile. int? (shorthand for Nullable<int>) opts a value type into
carrying "no value" explicitly, rather than a caller inventing a
sentinel like -1 to mean the same thing.
int? maybeAge = null;
if (maybeAge.HasValue)
Console.WriteLine(maybeAge.Value);
int age = maybeAge ?? 0; // ?? supplies a default when it is null
Reference types are nullable by default — string, List<int>, any
class — which is exactly what nullable reference types (string? under
#nullable enable) exist to push back on, by making the compiler warn
when a reference that was not declared ? might be null.
Try it yourself
2 visible tests · 2 hidden testsImplement FindMax(List<int> values), returning int?. Return the
largest value in the list, or null if the list is empty — never use
a sentinel like -1 or int.MinValue, since either could be a
genuine value in the list.
FindMax([3,7,2,9,4])FindMax([])
Sign up to check the hidden tests and save your progress. Sign up