Record types
Record types are introduced in c# 9 which allows writing immutable reference types. The properties of an instance of reference type cannot change after its initialization.
public record User(string FirstName , string LastName);
var user = new User("Steve", "Jobs");
If we try to change the property value after object initialization we will get compiler error
user.FirstName = "Mark"; //Compiler error
Non-destructive mutation using With
The only way to modify the properties of an immutable record instance that is already initialized is to create a new record instance and modify its properties at the moment of instantiation.
We can use “With” to specify only the properties we need to change when creating a new variable.
var user = new User("Steve", "Jobs");
var user2 = user with { LastName = "Smith" };
Similarly, we can even use with to copy an existing record:
var user = new User("Steve", "Jobs");
var user2 = user with { };
var isequal = user.Equals(user2); // True
Inheritance
A record can inherit from another record.
public record User(string FirstName , string LastName);
public record PrimeUser(string FirstName , string LastName , string Email) : User(FirstName , LastName);
var Primeuser = new PrimeUser("Steve", "Jobs", "steve@jobs.com");
Record Type Internals - Compiler generated code
Record type is a compiler feature . The compiler will take the Record type and generate it us
public class User : IEquatable<User>
{
[CompilerGenerated]
private readonly string <FirstName>k__BackingField;
[CompilerGenerated]
private readonly string <LastName>k__BackingField;
[System.Runtime.CompilerServices.Nullable(1)]
protected virtual Type EqualityContract
{
[System.Runtime.CompilerServices.NullableContext(1)]
[CompilerGenerated]
get
{
return typeof(User);
}
}
public string FirstName
{
[CompilerGenerated]
get
{
return <FirstName>k__BackingField;
}
[CompilerGenerated]
init
{
<FirstName>k__BackingField = value;
}
}
public string LastName
{
[CompilerGenerated]
get
{
return <LastName>k__BackingField;
}
[CompilerGenerated]
init
{
<LastName>k__BackingField = value;
}
}
public User(string FirstName, string LastName)
{
<FirstName>k__BackingField = FirstName;
<LastName>k__BackingField = LastName;
base..ctor();
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("User");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
[System.Runtime.CompilerServices.NullableContext(1)]
protected virtual bool PrintMembers(StringBuilder builder)
{
builder.Append("FirstName");
builder.Append(" = ");
builder.Append((object)FirstName);
builder.Append(", ");
builder.Append("LastName");
builder.Append(" = ");
builder.Append((object)LastName);
return true;
}
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator !=(User left, User right)
{
return !(left == right);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public static bool operator ==(User left, User right)
{
if ((object)left != right)
{
if ((object)left != null)
{
return left.Equals(right);
}
return false;
}
return true;
}
public override int GetHashCode()
{
return (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(<FirstName>k__BackingField)) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(<LastName>k__BackingField);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public override bool Equals(object obj)
{
return Equals(obj as User);
}
[System.Runtime.CompilerServices.NullableContext(2)]
public virtual bool Equals(User other)
{
if ((object)this != other)
{
if ((object)other != null && EqualityContract == other.EqualityContract && EqualityComparer<string>.Default.Equals(<FirstName>k__BackingField, other.<FirstName>k__BackingField))
{
return EqualityComparer<string>.Default.Equals(<LastName>k__BackingField, other.<LastName>k__BackingField);
}
return false;
}
return true;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public virtual User <Clone>$()
{
return new User(this);
}
protected User([System.Runtime.CompilerServices.Nullable(1)] User original)
{
<FirstName>k__BackingField = original.<FirstName>k__BackingField;
<LastName>k__BackingField = original.<LastName>k__BackingField;
}
public void Deconstruct(out string FirstName, out string LastName)
{
FirstName = this.FirstName;
LastName = this.LastName;
}
}
In the above compiler generated code the overridden Equal and Hashcode implementation helps value based equality.
var user = new User("Steve", "Jobs");
var user2 = new User("Steve", "Jobs");
Console.WriteLine(user.Equals(user2)); // True
Despite being a class, a record type provides additional value-like behavior and semantics that differentiate it from a class. Records are reference types, but they have their own built-in equality check - the equality is checked by value rather than reference.