Osy#betaRun it on Osyrin: osyrin.com

Reference / Class

Records

record Name(Type Member, …); — a class compared by its members

A `record` is a class whose values compare by their MEMBERS rather than by identity, exactly as a C# record does. The positional form `record Person(string Name, int Age);` declares the members, a constructor that takes them and a `Deconstruct`; every record gets `==`, `Equals`, `GetHashCode` over its members, `with`, and a `ToString` that lists them. Reach for one when two separately built values with the same contents should be the same value.

preview3 examples compiled by CIclasstypesauthoring

Summary#

A positional record, compared, copied and taken apart:

record Person(string Name, int Age);

string Describe() {
  var a = new Person("Ada", 36);
  var b = new Person("Ada", 36);
  var older = a with { Age = 37 };
  var (name, age) = older;
  return $"{a == b} {name} {age} {a}";   // "True Ada 37 Person { Name = Ada, Age = 36 }"
}

Signature#

record Name(Type A, Type B = default);            // positional: A and B are init-only properties
record Name(Type A) { /* more members */ }        // positional, with a body
record Name { public Type A { get; init; } }      // members declared the ordinary way
record Name(Type A) : Base(A);                    // derived: the arguments go to the base's constructor
sealed record Name(…);  public record Name(…);    // the class modifiers, as on a class

Description#

What a positional record declares#

record Person(string Name, int Age); is the class C# compiles it to:

  • a property per parameter, public string Name { get; init; } — set when the record is made, and not after;
  • a constructor taking the parameters in order, new Person("Ada", 36);
  • Deconstruct, so var (name, age) = person; takes it apart.

A parameter may have a default (record Page(int Size = 20);). Any other member goes in a body, exactly as on a class.

What every record gets#

  • Value equality. ==, != and Equals compare every member ([[class-equality#field-comparison]]), and GetHashCode agrees with them — so Distinct(), Contains, IndexOf and a dictionary key treat two equal records as one. Two records of different types are never equal.
  • with. person with { Age = 37 } is a copy with those members replaced (Copying a class with changes).
  • ToString. Person { Name = Ada, Age = 36 } — the type's name and each member in declaration order, as C# writes it. Override it (public override string ToString() => Name;) to say something else.

When to use one#

When the VALUE is what matters and two equal ones are interchangeable: a money amount, a date range, a column of a grid, a filter in a list. A value rebuilt on every render still equals the one you kept, because a record compares what it holds. Keep a plain class for a thing with an identity of its own, and an entity for anything stored.

A record struct#

record struct Pos(int X, int Y); is a struct with a record's members — copied where it is stored, and compared with == (a plain struct has no ==). Its positional members can be set ({ get; set; }); write readonly record struct to make them init-only.

Examples#

record Tag(string Label);

int Distinct() {
  var tags = new List<Tag> { new Tag("red"), new Tag("blue"), new Tag("red") };
  return tags.Distinct().Count();          // 2
}
record Range(int Low, int High) {
  public int Width => High - Low;
  public bool Contains(int n) { return n >= Low && n <= High; }
}

int Measure() {
  var r = new Range(3, 10);
  return r.Contains(5) ? r.Width : 0;      // 7
}

Errors#

MessageCauseFix
'Person.Name' is init-onlyA positional member was assigned after the record was made.Make a copy: p with { Name = "b" }.
an attribute on a positional parameter of 'X' is not supported yet[property: …] on a positional parameter.Declare that member in the record's body, with its attributes.

See also#

Related

Comparing classes

`==` on two `class` values asks whether they are the SAME INSTANCE, exactly as C# does: two objects built separately…

Structs

A `struct` is a VALUE type, as in C#: assigning one, passing one, returning one or putting one in a list stores a COPY…

Copying a class with changes

`with` makes a COPY of a class value and replaces the fields you name. Everything you do not name comes from the value…

Classes

A class is an in-memory shape — data plus the behaviour that belongs to it — and it never touches the database. That is…

class properties

A class member that reads and writes like a field but runs a body on access — a computed value, a validating setter, or…

constructor

A class declares one constructor — its name is the class name, it takes no return type, and it runs when you write new…