Summary#
A struct copies where a class would share:
struct Point { public int X; public int Y; }
int Copy() {
Point a = new Point();
a.X = 1;
Point b = a; // a copy
b.X = 2;
return a.X; // 1 — `a` was never touched
}Signature#
struct Name { public int X; public Name(int x) { X = x; } } // fields, constructors, methods — as a class
readonly struct Name { … } // C#'s promise that it is not changed after it is made
record struct Name(int X, int Y); // a struct with a record's members, == included
Name n; // a struct local is its DEFAULT, every member zero — `n.X = 3;` may follow
a.Equals(b) // member by memberDescription#
Where the copy happens#
Everywhere a struct is STORED: a local (Point b = a;), an assignment, an argument (the callee changes its own
copy), a returned value, a field of another type, and an element of a collection (points.Add(p) adds a copy).
Writing a member through the place that holds it changes that place — a.X = 1;, holder.Position.X = 5; — which
is what makes a struct field of a class usable at all.
Its default#
A struct is never null. A field of a struct type with no initializer starts at the struct's default — every member
at its own default — and so does a local declared without a value. Point? p; is the spelling for "maybe none".
Equality#
a.Equals(b) compares every member, and Distinct, Contains and a dictionary key use the same rule. A plain
struct has no ==, as in C# — write Equals, or declare it a record struct, which has both.
readonly#
readonly struct and a struct's readonly method (public readonly int Sum() => X + Y;) are accepted where C#
accepts them. Every call already reads the value as it stands, so neither changes an answer.
Not a ref struct#
A ref struct is confined to the stack, and an Osy# body can suspend and resume later, on another machine — there
is no stack to confine it to. It is refused by name; declare a struct.
Examples#
struct Point { public int X; public int Y; }
class Shape { public Point Origin; }
int Place() {
var s = new Shape(); // Origin is (0, 0), not null
s.Origin.X = 5; // writes the field itself
var list = new List<Point>();
list.Add(s.Origin); // a copy goes into the list
s.Origin.X = 9;
return list[0].X; // 5
}record struct Pos(int X, int Y);
bool Same() {
var p = new Pos(1, 2);
return p == new Pos(1, 2) && (p with { Y = 3 }).Y == 3; // true
}Errors#
| Message | Cause | Fix |
|---|---|---|
`==` is not defined on the struct 'X' | A plain struct has no ==, as in C#. | Write a.Equals(b), or declare it record struct X. |
Osy# has no `ref struct` | A stack-only type has no stack to live on in a durable body. | Declare a struct. |
`struct X(…)` — a struct's parameter list is a primary constructor | A plain struct with a parameter list. | Write record struct X(…), or declare the fields and a constructor. |
See also#
- Records — compared by members, and the
record struct - Comparing classes — identity for a class, members for a record or a struct
- Classes — classes, and when to use one
- Copying a class with changes — a copy with some members replaced