Class, struct, or record: the three-way choice that mutates 4,000 points
A hands-on C# tutorial for computational designers: when a coordinate should copy and a model element should be shared — value vs reference, on 4,000 points.
Ask a physicist-turned-programmer what a type is and you get a clean answer: a blueprint for a block of memory. Microsoft’s own C# documentation opens exactly there — a class, struct, or record “is like a blueprint that specifies what the type can do,” and an object is “a block of memory allocated and configured according to the blueprint.” That one sentence hides the most consequential decision a computational designer makes before writing a single line of geometry: whether the thing you are modelling gets copied or gets shared. Every office that writes its first C# component eventually hits the same wall — a list of setting-out points that stubbornly refuses to move.
←TODAY: C# 14.0 shipped on 11 November 2025; class vs struct vs record is a compile-time choice, not a runtime accident. →3012: the studios still standing are the ones whose value-vs-reference discipline was written down, not remembered per Praktikant. Fulcrum: a coordinate that should copy and a model element that should be shared want opposite type kinds — you only see it clearly once you watch both fail.
The Tool: This tutorial is built on the free, open-source .NET SDK and its dotnet command-line tool — Microsoft’s own toolchain for C#, the language designed by Anders Hejlsberg and now stewarded by Mads Torgersen. Per Wikipedia’s C# language page, the language first appeared in July 2000 and reached stable release 14.0 on 11 November 2025; the Roslyn compiler behind it is MIT-licensed. It earns an architect’s afternoon for one reason: C# is the language of the Grasshopper C# component and RhinoCommon — where code meets geometry in a Rhino-based studio. Learn it in a plain Windows console app first, and the Grasshopper component stops being intimidating.
System: the blueprint has rules you inherit
The Microsoft docs name encapsulation as “the first pillar” of object-oriented programming, and they are blunt about the default: unless you say otherwise, a member’s accessibility is private. Beyond that, the three blueprints diverge on purpose. Only classes support inheritance — a class can derive from a base class and pick up its public, protected, and internal members. Structs cannot inherit at all. Records add value-based equality: two records are equal when, for the same type, every field is equal, while two class instances are equal only when they point at the same object. The docs list three more record features that make them the right choice for read-mostly data: a with expression for nondestructive mutation (a copy with named properties changed), a built-in ToString that prints the type name and its public property values, and — the asymmetry to memorise — record classes support inheritance while record structs do not. That reference-vs-value split is the whole story, and it is why the choice is a design decision, not a syntax preference.
Setup:
# Windows 11, PowerShell
winget install Microsoft.DotNet.SDK.9
dotnet --version # 9.0.x confirms the SDK is live
dotnet new console -n SetOut
cd SetOut
dotnet run # prints: Hello, World!First steps:
- Open
Program.cs. Below the generated line, declare a point as a struct:struct Pt { public double X; public double Y; }. - Make a small
List<Pt>, loop over it, and try to nudge eachXby 10. Rundotnet runand read the compiler’s complaint. - Change the single word
structtoclass, run again, and watch the same loop suddenly mutate in place. That flip — same code, opposite behaviour — is the entire lesson.
Street: what copies, what is shared
A class is a reference type: two variables can point at one object, the way the reigning Women’s World Cup holders Spain (2023) all read from a single team sheet — change the sheet, everyone sees it. A struct is a value type: every variable gets its own photocopy. Loop over 4,000 setting-out points stored as structs, nudge each one, and the list never changes, because you mutated 4,000 discarded copies. FIFA runs its own game from a hillside above Zurich; our studio, three tram stops away in Kreis 5, runs the same confusion in code. The Grasshopper C# scripting guide by Ehsan Iran-Nejad at McNeel is careful to keep language syntax and the Rhino API separate for exactly this reason: the aliasing bug is a C# fact, not a Rhino one.
Atelier: In a Rhino-based Swiss studio the working rule is short: a coordinate, a parameter set, or a lightweight tuple wants a struct or a record — cheap, copied, no surprise aliasing; a model element that many components read and mutate wants a class. The PAZ Grasshopper↔Archicad Library leans on precisely this discipline so a component’s outputs never silently alias its inputs. Your Monday move: open your most-reused C# component and grep for any struct you mutate inside a foreach — that is a copy you are throwing on the floor, and it belongs on the fix list before the next Wettbewerb deadline.
Hack: Watch a coordinate silently refuse to move inside a loop, then make it move on purpose. This is value semantics doing its job — the struct hands you a copy, so the compiler refuses the misleading write and forces an explicit read-edit-writeback.
struct Pt { public double X; public double Y; }
var pts = new List<Pt> { new() { X = 0, Y = 0 } };
foreach (var p in pts) p.X += 10; // compile error: p is a read-only copy
for (int i = 0; i < pts.Count; i++) // the honest fix
{ var q = pts[i]; q.X += 10; pts[i] = q; } // read, edit, write backSwap struct for class and the first loop compiles and mutates in place — convenient right up to the moment two components share the same object and one quietly rewrites the other’s geometry.
Move — Learn-it:
- Repo/reference: Grasshopper C# scripting guide — developer.rhino3d.com/guides/scripting/scripting-gh-csharp (Ehsan Iran-Nejad, McNeel)
- Docs: Classes, structs, and records — Microsoft Learn
- Root concept: C# (programming language) — Wikipedia
- PAZ note: when the class/struct/record habit sticks, the Grasshopper C# component is the natural next step — a separate PAZ special covers that seam.
SOURCE · ↗
PAZ Kaffi · multidisciplinary editorial, led by PAZ Academy