C#抽象Record转主构造函数后,派生类可改Type属性是Rider Bug吗?
问题:Rider的主构造函数转换建议是否存在Bug?
原始代码
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的转换建议
JetBrains Rider建议将上述代码中的基类Transaction转换为使用主构造函数的形式,转换后的代码如下:
public abstract record Transaction(TransactionType Type) { }
转换后的问题
转换后出现了不符合原始设计意图的问题:派生记录类型的实例可以在初始化时修改Type属性,而原始代码中这是不允许的。示例代码如下:
var inheritedTransaction = new InheritedTransactionBarOnly { Type = TransactionType.Foo // 转换后可编译,转换前不允许 };
解答
这不是你对C#主构造函数的理解错误,而是Rider的转换建议存在缺陷。
原因如下:
- 原始代码中,
Type是只读自动属性(仅包含get访问器),仅能在基类构造函数中赋值,派生类通过无参构造函数固定传入TransactionType.Bar,外部完全无法修改该属性。 - 使用主构造函数定义抽象记录时,C#编译器默认会为构造函数参数生成带有
init访问器的自动属性。init访问器允许在对象初始化器中修改属性值,这直接破坏了原始代码的设计逻辑——确保InheritedTransactionBarOnly的Type始终为Bar。
正确的转换方式应该是手动显式定义只读属性,避免编译器生成可初始化的属性:
public abstract record Transaction(TransactionType type) { public TransactionType Type { get; } = type; }
这种方式保留了原始代码的只读特性,外部无法通过任何方式修改Type属性,符合最初的设计意图。
内容的提问来源于stack exchange,提问作者Ilya Chernomordik
相关产品推荐
相关产品推荐

