1

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.

Kia Panahi Rad
  • 1,235
  • 2
  • 11
  • 22
  • 6
    I'm still unclear on what you think the contradiction is and where the problem supposedly lies. Clearly the `with` expression invokes the clone method, clearly the clone method invokes the (synthesized) copy constructor. Perhaps you were under the impression that it was supposed to invoke *your* constructor, but the spec doesn't say that anywhere. If you want custom constructor logic, declare your own copy constructor (taking a single `Person` parameter). – Jeroen Mostert Nov 26 '21 at 09:42
  • 1
    As soon as you want custom constructor logic records get clunky because you can't do much in the way of chaining constructors, so a bit of code duplication becomes inevitable (i.e. both your copy constructor and your regular constructor need to assign all the properties themselves and increment the call counter, though the latter can be refactored into its own method). – Jeroen Mostert Nov 26 '21 at 10:04

1 Answers1

0

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++;
    ...
Dharman
  • 30,962
  • 25
  • 85
  • 135
PaulG
  • 111
  • 6