11

In C# 9, one can define a property with the same name in a record both in its primary constructor and in its body:

record Cat(int PawCount)
{
    public int PawCount { get; init; }
}

This code compiles without errors.

When initializing an instance of such a record, the value provided to the constructor is completely ignored:

Console.WriteLine(new Cat(4));
Console.WriteLine(new Cat(4) { PawCount = 1 });

prints

Cat { PawCount = 0 }
Cat { PawCount = 1 }

Is this behavior correct or is it a bug? If it’s correct, what are the cases in which it is useful?

I expected the compiler to either reject this code with an error like ‘The type Cat already contains a definition for PawCount or consider the property in the constructor and in the body the same, performing its initialization from the constructor. The latter variant could be useful to provide the property with a custom getter and/or initializer without having to rewrite all the properties of the positional record in its body.

The actual behavior makes no sense to me.

user1067514
  • 494
  • 3
  • 15
  • 4
    There aren't multiple properties. In the second example you *are* replacing the value of the property during object initialization – Panagiotis Kanavos Dec 02 '20 at 15:05
  • 2
    The first one is weird though - it's behaving as if you tried to assign the default value – Panagiotis Kanavos Dec 02 '20 at 15:06
  • 1
    https://stackoverflow.com/questions/17327266/constructor-vs-object-initializer-precedence-in-c-sharp/17327340 – Jonesopolis Dec 02 '20 at 15:07
  • It is correct. You have to print the same instancue you r set. Do something like this : Cat newCat = new Cat(4) { PawCount = 1 }; Console.WriteLine(newCat); – jdweng Dec 02 '20 at 15:07
  • Remove the property declaration and it works as expected. My assumption is that since you did part of the work it can do for you it decided to not do any of it (creating a `PawCount` property and assining it from the constructor parameter). But I might agree that it should either assign the value if it already finds a declared property with the same name or not compile. https://dotnetfiddle.net/UbS1i2 –  Dec 02 '20 at 15:25

1 Answers1

15

The correct way to do this is:

record Cat(int PawCount)
{
    public int PawCount { get; init; } = PawCount;
}

This is useful as it allows you to do e.g. validation

record Cat(int PawCount)
{
    private int _pawCount;
    public int PawCount {
        get => _pawCount;
        init => _pawCount = value < 0 ? throw new ArgumentException() : value; 
    } = PawCount;
}

The spec for this is here: https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md#members-of-a-record-type

Members are synthesized unless a member with a "matching" signature is declared in the record body or an accessible concrete non-virtual member with a "matching" signature is inherited. Two members are considered matching if they have the same signature or would be considered "hiding" in an inheritance scenario.

So since a property with the same name as the parameter already exists, the compiler wont synthesize a PawCount property, and so just silently ignores the parameter unless you use it yourself explicitly.

Yair Halberstadt
  • 5,733
  • 28
  • 60
  • Where can I find the documentation on this? – user1067514 Dec 02 '20 at 15:34
  • 2
    https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/records.md#members-of-a-record-type "Members are synthesized unless a member with a "matching" signature is declared in the record body or an accessible concrete non-virtual member with a "matching" signature is inherited. Two members are considered matching if they have the same signature or would be considered "hiding" in an inheritance scenario." – Yair Halberstadt Dec 02 '20 at 15:38
  • I don't think your example is correct... You have to set the initializer on the backing field. – doterik Dec 02 '21 at 21:03