Change owners, step through generations, pinning and finalization, or compare memory under load. The model explains concepts; the SDK examples measure real runtime observations.
Open the Memory Lab ↗The memory contract
Allocation and assignment are related but different operations. A class construction creates an object whose reference can be stored in several variables. Assigning that reference does not construct a second independent object. A struct assignment copies its value, although a struct can itself contain references that remain shared after copying. These language semantics matter before any allocation optimization is considered: a design that misunderstands aliasing can corrupt state even if a profiler reports fewer bytes. Avoid reducing the rule to an inaccurate stack-versus-heap slogan that ignores fields, arrays, captures, and boxing.
Trace the actual program
The Counter construction creates one object with Value two. Assigning first to alias makes both variables refer to it, so updating through alias makes the first output 8. Point is a struct containing an integer; copying it gives another independent X value. Updating copy therefore leaves value.X at two, which is the second output. ReferenceEquals then confirms that first and alias identify the same Counter, printing True. The program does not measure the number of physical allocations a particular optimizer ultimately performs.
How the mechanism works
A runtime may optimize storage when it can preserve every observable behavior, but it cannot turn a shared mutable Counter into two independently changing logical objects. Likewise, the value-copy behavior of Point does not guarantee every value type lives in a stack frame. A Point can be embedded in another object or stored in an array. When a struct contains a list reference, copying the struct copies that reference too. Separate the container's value semantics from the ownership and mutability of objects reachable through its fields.
Boundaries and misconceptions
Replacing alias with new Counter is a different operation from changing alias.Value. The first replaces one reference slot; the second mutates the object both references currently share. A profiler can help identify expensive construction sites, but allocation counts alone cannot establish that a proposed pooling or copying change preserves behavior. First write the required identity and mutation rules. Then evaluate whether a new storage strategy satisfies them on the actual target runtime rather than importing a desktop benchmark's conclusion into the browser.
Practice: change one ownership path
First replace the alias assignment with construction of another Counter initialized to two. Keep the later mutation and predict first.Value two and ReferenceEquals false. Hint: count the logical construction expressions and draw separate objects. Restore the original before a second variation. Then copy the reference normally but reassign alias to a new Counter before changing its Value. Explain why first again remains two: the variable's target changed, not the original object's field. These edits isolate identity from the numeric payload.
Practice: test a second boundary
Next add a List of integers as a field of a separate struct, copy that struct, and append through the copied field. Predict that both structs can observe the same list contents even though their independent integer fields still copy by value. Hint: draw the reference stored inside each struct rather than assuming the struct copy recursively duplicates everything. Write down whether your desired API promises a shallow copy, an independent snapshot, or immutable reachable data before deciding which implementation is appropriate.
Review the evidence
Use the unchanged outputs eight, two, and true to verify your explanation of assignment. Then identify which behavior is a language-level contract and which questions require runtime measurement. This distinction is foundational for memory work: removing an unnecessary allocation may be useful, but removing a necessary ownership boundary can be a correctness regression. Reachability diagrams, mutation tests, and target-specific allocation evidence complement one another; none should be treated as an automatic substitute for the others.
- The original Counter is shared, so its printed Value becomes eight.
- The original Point keeps X two after the copied value changes.
- Logical copying rules do not specify one universal physical storage location.
A working example
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
var first = new Counter { Value = 2 };
var alias = first;
alias.Value = 8;
var value = new Point { X = 2 };
var copy = value;
copy.X = 8;
Console.WriteLine(first.Value);
Console.WriteLine(value.X);
Console.WriteLine(ReferenceEquals(first, alias));
sealed class Counter { public int Value; }
struct Point { public int X; }
Expected output
8 2 True
The original Counter is shared, so its printed Value becomes eight.