协变返回类型的自动属性在构造函数初始化后仍为null问题
问题成因
- 该现象是C#中只读自动属性与协变返回类型重写共同作用的必然结果,核心原因是父子类的自动属性各自对应了独立的支持字段(backing field):
- 基类
BaseClass的virtual BaseProperty MyProperty { get; }是只读自动属性,编译器会为它生成一个仅能在基类构造函数中赋值的私有支持字段。 - 子类
FinalClass用协变返回类型override FinalProperty MyProperty { get; }重写属性时,编译器会为这个重写的只读自动属性另外生成一个属于子类的独立支持字段,不会复用基类的字段。 - 你在基类构造函数中执行的
MyProperty = prop,赋值的是基类自己的支持字段,子类的支持字段从未被赋值,默认值就是null。 - 当访问
FinalClass实例的MyProperty时,因为属性已被重写,实际调用的是子类的getter,读取的是子类未赋值的支持字段,自然返回null,哪怕基类的字段已经有值也不会被用到。
- 基类
你注释中的临时方案能生效,是因为手动实现了getter,直接读取基类属性值再做类型转换,绕开了子类自动生成的支持字段。
更优解决方案
以下方案均比临时手动类型转换更安全、更符合设计规范:
方案1:给子类自动属性显式赋值(推荐,适配C# 9+)
在子类构造函数中直接给子类的重写属性赋值即可,不需要修改基类代码:
public class FinalClass : BaseClass { // 直接用FinalProperty作为参数类型可从语法层面避免类型转换异常 public FinalClass(FinalProperty prop) : base(prop) { // 给子类自己的自动属性支持字段赋值 MyProperty = prop; } public override FinalProperty MyProperty { get; } }
如果必须兼容基类的BaseProperty参数,可补充类型校验:
public FinalClass(BaseProperty prop) : base(prop) { MyProperty = prop as FinalProperty ?? throw new ArgumentException("参数必须为FinalProperty类型", nameof(prop)); }
方案2:基类属性改为抽象属性,统一访问逻辑
如果基类本身不需要独立的支持字段,可将基类属性设为抽象,从设计层面避免多字段冲突问题:
public abstract class BaseClass { public BaseClass(BaseProperty prop) { // 这里赋值的是子类重写的属性,直接写入子类的支持字段 MyProperty = prop; Console.WriteLine($"ctor A : {MyProperty == null}"); } // 改为抽象属性,基类不生成支持字段 public abstract BaseProperty MyProperty { get; set; } } public class FinalClass : BaseClass { public FinalClass(FinalProperty prop) : base(prop) { } // 协变重写,自动属性的setter对应子类的支持字段 public override FinalProperty MyProperty { get; set; } }
方案3:子类重写属性直接复用基类字段
如果不想修改基类逻辑,也可以不用自动属性,直接重写getter返回基类属性值,比临时转换写法更规范:
public class FinalClass : BaseClass { public FinalClass(BaseProperty prop) : base(prop) { } // 直接返回基类属性,不需要生成子类的支持字段 public override FinalProperty MyProperty => (FinalProperty)base.MyProperty; }
内容的提问来源于stack exchange,提问作者R1 Tech
相关产品推荐
相关产品推荐

