Duplicating .NET records does not call the constructor

385 Views Asked by At

In the new .NET type, record, one has the ability to duplicate a record with some optional modifications to the duplicate's properties.

Considering the following two observations, I have come to a contradiction that I am unable to get my head around.


Observation #1

Duplication mechanism

The duplication is being supported by the compiler-generated instance <Clone>$() method on the record type.

Decompiling a sample record type shows that the <Clone>$() method calls the record's ctor internally.

(Decompiled code)

[System.Runtime.CompilerServices.NullableContext(1)]
public virtual Person <Clone>$()
{
    return new Person(this);
}

Observation #2

with expression calls the <Clone>$() method

As mentioned here, the with expression is invoking the receiver's <Clone>$() method before proceeding to modify the properties.

First, the receiver's "clone" method (specified above) is invoked and its result is converted to the receiver's type. Then, each member_initializer is processed the same way as an assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order.


However, I have noticed that despite being exclusively mentioned that the <Clone>$() method is called in a with statement, the actual behaviour is that the constructor is not being called.

Here is a Gist and link to the shaplap.io if anyone's interested.

1

There are 1 best solutions below

0
On

In your examples (sharplap.io) the Person constructor is being called.

Per the code you identified:

[System.Runtime.CompilerServices.NullableContext(1)]
public virtual Person <Clone>$()
{
    return new Person(this);
}

This is calling the constructor with the signature:

protected Person([System.Runtime.CompilerServices.Nullable(1)] Person original)
{
    <Id>k__BackingField = original.<Id>k__BackingField;
    <Name>k__BackingField = original.<Name>k__BackingField;
    <Addresses>k__BackingField = original.<Addresses>k__BackingField;
}

This is not the constructor you defined in the Person record, however you can override/provide your own implementation of this constructor an increment the calls value.

record Person
{
    private static volatile int calls = 0;
    public static int Calls => calls;
    
    public Person(Person person)
    {
        calls++;
    ...