Consider the following code:
public enum TransactionType{Foo,Bar}
public abstract record Transaction
{
public TransactionType Type { get; }
protected Transaction(TransactionType type)
{
Type = type;
}
}
public record InheritedTransactionBarOnly: Transaction
{
public InheritedTransactionBarOnly(): base(TransactionType.Bar){}
}
public static class Program
{
public static void Main()
{
var iWantThis = new InheritedTransactionBarOnly();
//var iDontWantThis = new InheritedTransactionBarOnly{Type = TransactionType.Foo}; // this shouldn't be possible
}
}
Rider is suggesting to convert to a primary constructor:
but I do believe this is wrong, as this will allow for Type
to be set during the initialization of the inherited record:
var inheritedTransaction = new InheritedTransactionBarOnly
{
Type = TransactionType.Foo // This code compiles only after Rider suggested change, but not before
}
Is my understanding is wrong or it is just a bug in Rider?
P.S. This is what Transaction looks like after Rider's conversion:
public abstract record Transaction(TransactionType Type)
{
}
I made a few test cases to show the difference between how record
s are built. The following is just for class records, I'm not sure if the same is true for struct records.
Starting with your example, SomeProperty { get; }
this means the property can only be set during construction, not from new { SomeProperty = ... }
code. But record primary constructor (the refactor suggestion) declares properties {get; init;}
by default, which allows setting in new ...
blocks. However, note that you can redefine properties inside the declaration; see the AltRecord
record below. Perhaps this is more concise...
See record reference in the section "If the generated auto-implemented property definition isn't what you want" ...
So to answer your question, yes, this seems like a mistake in the refactor, because behavior is not the same. Here is some code to show the differences.
public abstract record GetOnlyExample
{
// Readonly property (cannot set in new { SomeProperty = ... } )
public int SomeProperty { get; }
// Property can only be set once, from protected constructor.
protected GetOnlyExample(int someProperty)
{
SomeProperty = someProperty;
}
}
public record ConcreteGetOnlyExample : GetOnlyExample
{
// Concrete implementation, can only set SomeProperty through base constructor.
public ConcreteGetOnlyExample() : base(1) { }
}
public abstract record GetInitExample
{
// Readonly property, allow in init (can set in new { SomeProperty = ... } )
public int SomeProperty { get; init; }
// Property can only be set during construction, or init.
protected GetInitExample(int someProperty)
{
SomeProperty = someProperty;
}
}
public record ConcreteGetInitExample : GetInitExample
{
// Concrete implementation, can only set SomeProperty during init/construction.
public ConcreteGetInitExample() : base(1) { }
}
// Primary constructor, creates: public int SomeProperty { get; init; }
public abstract record PrimaryConstructor(int SomeProperty)
{
}
public record ConcretePrimaryConstructor : PrimaryConstructor
{
// Concrete implementation, can only set SomeProperty during init/construction.
public ConcretePrimaryConstructor() : base(1) { }
}
// Primary constructor, creates: public int SomeProperty { get; init; }
public abstract record AltRecord(int SomeProperty)
{
// redefine SomeProperty to be readonly (no init)
public int SomeProperty { get; } = SomeProperty;
}
public record ConcreteAltRecord : AltRecord
{
public ConcreteAltRecord() : base(1) { }
}
internal class Program
{
static void Main(string[] args)
{
// compile error: not allowed during init:
// ConcreteGetOnlyExample aaa = new ConcreteGetOnlyExample() { SomeProperty = 5 };
// can set during init:
ConcreteGetInitExample bbb = new ConcreteGetInitExample() { SomeProperty = 5 };
// implicit init created through primary constructor:
ConcretePrimaryConstructor ccc = new ConcretePrimaryConstructor() { SomeProperty = 8 };
// compile error: SomeProperty was redefined as `get` only, can't set during init
//ConcreteAltRecord ddd = new ConcreteAltRecord() { SomeProperty = 9 };
}
}