variablesheap$ dotnet run1Coord { X = 9, Y = 2 }Truea.Xa.Yr1var a = new Point { X = 1, Y = 2 };2var b = a;3b.X = 9;4Console.WriteLine(a.X);5var r = new Coord(1, 2);6var s = r with { X = 9 };7Console.WriteLine(s);8var t = new Coord(1, 2);9Console.WriteLine(r == t);10struct Point { public int X, Y; }11record Coord(int X, int Y);X = 1 Y = 2#1 · Coordsb.Xb.YX = 9 Y = 2#2 · CoordtX = 1 Y = 2#3 · Coord1 · 291#1#2#3copyCoord { … }True
Variables: a and b hold their values; r, s, t hold references (#n)Heap: the Coord record objects, with arrows from their referencesTokens: a copied value, a new value, references and printed text movingFront left: the program, Program.cs. Back left: the console. Middle: the variables. a and b are structs, so each holds its own X and Y; r, s and t hold references. Right: the heap, where each new record object lands. Assigning a struct copies its fields; with copies a record into a new object; == on records compares values. Local variables such as a and b usually live on the stack, but that is a detail of the runtime, not of C#. What C# promises is that a value-type variable holds its own copy of the data.

Two kinds of type

  1. Lines 10 and 11 declare two small types that each bundle an X and a Y. Point is a struct, a value type. Coord is a record: a class, so a reference type, that the compiler fills with extra code.

Structs: values

Records: data on the heap

Choosing

What happens

// step 1/9 · types
line 10  struct Point { public int X, Y; }
         struct  → a value type (like int)
line 11  record Coord(int X, int Y);
         record  → record class → a reference type
         (X, Y)  → the compiler writes a constructor,
                   properties, ==, ToString and more

Local variables such as a and b usually live on the stack, but that is a detail of the runtime, not of C#. What C# promises is that a value-type variable holds its own copy of the data. The #1, #2 and #3 on the references stand for the objects' addresses; you never see or use real addresses in safe C#.