Summary#
A class is an in-memory shape: fields and the behaviour that belongs to them. It is not persisted and
has no table — that is the one line that separates it from an entity. Like a C# class it is a
reference type ([[class-index#reference|what that means for assignment]]). Reach for a class when
you need a structured value to compute with inside a function or a component: a parsed request, a calculation's
intermediate, a projection's target, a small bundle of related fields you pass around.
If you're deciding between the two: does it need to be stored and queried? Yes → an entity. No → a class.
Description#
A value, not a row#
An entity lives in the database; the platform loads it, tracks your edits, and commits them. A class does none of
that — you new it, read and write its fields, hand it to another function, and it vanishes when the work is done. No
schema, no migration, no security rules: it is just a value in memory, like any C# object. It carries its own
constructor and methods:
class Money {
public decimal Amount;
public string Currency;
// the ctor assigns the required Currency, so `new Money(9.5m, "USD")` needs no initializer — the compiler infers it
public Money(decimal amount, string currency) {
Amount = amount;
Currency = currency;
}
public string Label() => Amount.ToString("F2") + " " + Currency;
}Build one and read its label — the constructor runs, then the method runs on the value:
[Test]
void Builds_And_Labels() {
var m = new Money(9.5m, "USD");
Assert.Equal("9.50 USD", m.Label());
}Does var b = a; copy it, or point at it?#
It points at it. A class is a REFERENCE type, exactly like a C# class — "value" above describes what a class is
for (a shape you compute with, with no table behind it), never how assignment behaves. Three consequences, and all
three are the C# ones:
var b = a;makesbanother name for the same object. A write throughbis visible througha.list[i].Field = xsticks — the indexer hands back the object, not a copy of it, so you can edit rows in place.- A list you filtered holds the same objects as the list you filtered it from, so editing through one is visible through the other.
⚑ In a component, that in-place write also RE-RENDERS — a render slot reading a class field is tracked like any
other read, so you never need to reassign the list to make the screen move. What you cannot do is point a control's
two-way value: at a class field. Both halves, with compiled proof: [[ui-reactivity#class-values]].
class Ticket {
public string Code;
public decimal Price;
public Ticket(string code, decimal price) { Code = code; Price = price; }
}[Test]
void A_Class_Is_A_Reference() {
var a = new Ticket("T1", 10m);
var b = a;
b.Price = 99m;
Assert.Equal(99m, a.Price); // same object — `b` was never a copy
var rows = new List<Ticket>();
rows.Add(new Ticket("T2", 1m));
rows.Add(new Ticket("T3", 2m));
rows[0].Price = 42m;
Assert.Equal(42m, rows[0].Price); // an edit through the indexer sticks
var dear = rows.Where(t => t.Price > 1m).ToList();
dear[0].Price = 50m;
Assert.Equal(50m, rows[0].Price); // the filtered list holds the SAME objects
var missing = rows.FirstOrDefault(t => t.Code == "nope");
Assert.True(missing == null); // no match is null, not an empty Ticket
}⚑ with is the one place a copy happens, and that is the point of it: a with { Price = 5m } builds
a new object and leaves a alone. It is opt-in copying, not evidence that assignment copies.
It has a constructor#
A class declares one constructor — its name is the class name, it takes no return type, and it
runs when you write new T(args). The constructor body runs first; object-initializer syntax ({ Member = value })
applies after it. Use the constructor for the setup a valid instance always needs.
It has methods#
Behaviour that belongs to a shape lives on the shape. A method has a receiver and is called as
value.Method(...) — the natural home for logic that is about this value rather than about the database. A method
body resolves function-style with the class as its receiver, the same mechanism a component's action uses.
It has properties#
A property reads and writes like a field but runs a body on access — a value computed from other
fields (Total => Qty * Price), a setter that validates a write, or an auto-property ({ get; set; }) whose storage
the platform synthesizes. It is a field-shaped pair of methods, so it works everywhere a method does, the browser
included.
Its fields can have defaults#
A field can carry an initializer — decimal Rate = 0.25m;. It runs at construction, before the constructor body and
before any object initializer, exactly as in C#: the constructor (and every read) sees the declared value unless
something later overwrites it.
class Cart {
public decimal Rate = 0.25m;
}[Test]
void Field_Default_Applies() {
var c = new Cart();
Assert.Equal(0.25m, c.Rate); // the declared default, applied at construction
var d = new Cart { Rate = 0.1m };
Assert.Equal(0.1m, d.Rate); // an object initializer overrides it
}Visibility follows C##
A top-level class is internal by default (an entity or enum is public) — see type visibility (public / internal). Mark it
public when code in another namespace needs to name it.
See also#
- constructor — the single constructor and how it composes with object initializers
- Class inheritance — deriving one class from another, and
sealed - Testing which class a value is — asking which type a value actually is
- class methods — behaviour with a receiver, called
value.Method(…) - class properties — members that read/write like a field but run a body on access
readonlyfields — a field only the constructor may set- Copying a class with changes — copying a value and replacing some of its fields
- Method overloads — several methods sharing a name, told apart by their parameters
staticmethods — a method that belongs to the type rather than to an instanceparamsparameters — a method callable with any number of trailing arguments- entity — the persisted counterpart, when the shape needs a table
- type visibility (public / internal) — why a class defaults to
internal