Record types are a special reference type that provide synthesized methods for equality using value semantics and data encapsulation. Record types can be created with immutable or standard properties.
class Car
{
public string Make { get; set; }
public Car()
{
}
public Car(string make)
{
Make = make;
}
}
Immutable Record Types with Standard Property Syntax
Creating an immutable Car record type using standard property syntax is similar to creating classes with immutable properties.
record CarRecord
{
public string Make { get; init; }
public CarRecord()
{
}
public CarRecord(string make)
{
Make = make;
}
}
Note: Record types allow using the class keyword to help distinguish them from "record structs" but the keyword is optional. Therefore "record class" and record mean the same thing.
record class CarRecord = record CardRecord
Immutable Record Types with Positional Syntax
Abbreviated;
record CarRecord(string Make);
Refered to as a positional record type, the constructor defines the properties on the record, and all of the other plumbing code has been record. There are 3 considerations when using this syntax:
- You cannot use object initialization of record types using the compact definition system.
- Record must be constructed with the properties in the correct position.
- The casing of properties in the constructor is directly translated to the casing of the properties on the record type.
When using the positional syntax, record types provie a primary constructor that matches the positional parameters on the record declaration.
Deconstructing Mutable Record Types
Record types using positional parameters also provide Deconstruct() method with on "out" parameter for each positional parameter in the declaration.
CarRecord car = new CarRecord("Hyundai");
car.Deconstruct(out string make);
Console.WriteLine(make);
Mutable Record Types
C# also supports mutable record types by using standard (not init-only) setters.
record CarRecord
{
public string Make { get; set; }
public CarRecord()
{
}
public CarRecord(string make)
{
Make = make;
}
}
Value Equality with Record Types
Record types implicitly override Equals, == and != two record types are considered equal if the hold the same values and are the same type, just as if the instances are value types.
static void Main(string[] args)
{
CarRecord c = new CarRecord { Make = "Honda" };
CarRecord c2 = new CarRecord { Make = "Honda" };
Console.WriteLine(c.Equals(c2)); // True
Console.WriteLine(ReferenceEquals(c,c2)); // False
Console.WriteLine(c == c2); // True
Console.WriteLine(c != c2); // False
}
Notice that they are considered equal, even though the variables point to 2 different variables in memory.
Copying Record Types Using with Expressions
With record types, assigning a record type instance to a new variable creates a pointer to the same reference, which is the same behavior as classes.
CarRecord copy = c;
To create a true copy of a record with one or more properties modified (referred to as nondestructive mutation), C# 9.0 introduces "with" expressions. In the with construct, any properties that need to be updated are specified with their new values, and any properties not listed are shallow copied exactly.
CarRecord c3 = c with { Make = "Mercedes" };
Record Structs (New 10.0)
Record structs are the value type equivalent of record types. Record structs can also use positional parameters on standard property syntax and provide value equality, nondestructive mutation, and built-in display fomatting.
Mutable Record Structs
public record struct Point(double X, double Y); // Positional Syntax
public record struct Point() // Property Syntax
{
public double X { get; set; } = default;
public double Y { get; set; } = default;
public Point(double x, double y) : this()
{
X = x;
Y = y;
}
}
Immutable Record Structs
The previous 2 record struct examples can be made immutable by adding the "readonly" keyword.
Deconstructing Record Structs
Record structs that use positional syntax also provide a Deconstruct() method.
Point p = new Point(1,2);
p.Deconstruct(out double r1, out double r2);