When you use a record. you can create a new instance by using the new keyword. Or you can copy an instance with some modifications using with expression (non-destructive mutation). The with expression copy all fields from the original instance and then apply the modifications.
C#
var john = new Sample("John", "Doe");
var Jane = john with { FirstName = "Jane" };
record Person(string FirstName, string LastName);
If the record contains a mutable object, such as a List<T>, the instance will be shared between the original and the copy. This is because the with expression doesn't clone the mutable object. Here's an example:
C#
var john = new Person("John", "Doe") { PhoneNumbers = new() { "1234567890" } };
var jane = john with { FirstName = "Jane" };
jane.PhoneNumbers.Add("123");
// Both list are modified
Console.WriteLine(john.PhoneNumbers.Count); // 2
Console.WriteLine(jane.PhoneNumbers.Count); // 2
public record Person(string FirstName, string LastName)
{
public List<string>? PhoneNumbers { get; init; }
}
When you use the with expression, the C# compiler calls the generated <Clone>$ method. This method calls the copy constructor. If you don't implement a copy constructor, the compiler will generate one for you that copies all fields. Here's the generated code for the Person record:
C#
internal class Person
{
// ...
[CompilerGenerated]
public virtual Person <Clone>$()
{
return new Person(this);
}
[CompilerGenerated]
protected Person(Person original)
{
<FirstName>k__BackingField = original.<FirstName>k__BackingField;
<LastName>k__BackingField = original.<LastName>k__BackingField;
<PhoneNumbers>k__BackingField = original.<PhoneNumbers>k__BackingField;
}
}
If you want to clone the PhoneNumbers property when using the with expression, you can implement a copy constructor manually:
C#
public record Person(string FirstName, string LastName)
{
public List<string>? PhoneNumbers { get; init; }
protected Person(Person other)
{
FirstName = other.FirstName;
LastName = other.LastName;
if(other.PhoneNumbers != null)
{
PhoneNumbers = new List<string>(other.PhoneNumbers);
}
}
}
//
var john = new Person("John", "Doe") { PhoneNumbers = new() { "1234567890" } };
var jane = john with { FirstName = "Jane" };
jane.PhoneNumbers.Add("0987654321");
// The list is modified only in the copy
Console.WriteLine(john.PhoneNumbers.Count); // 1
Console.WriteLine(jane.PhoneNumbers.Count); // 2
Do you have a question or a suggestion about this post? Contact me!