C# 派生类如何将自身赋给基类的泛型属性?
数据小许
结论
可以为 GeneralTreeNode<V> 添加一个 protected 无参构造函数,并用 default! 初始化 Value。派生类的构造函数随后将 Value 设为 this。普通节点仍可通过现有构造函数传入值,需要延后赋值的派生类则使用无参构造函数。
public class GeneralTreeNode<V>
{
public virtual GeneralTreeNode<V>? Parent { get; set; }
public virtual List<GeneralTreeNode<V>> Children { get; set; } = new();
public virtual V Value { get; set; } = default!;
public GeneralTreeNode(V value, GeneralTreeNode<V>? parent = null)
{
Value = value;
Parent = parent;
}
protected GeneralTreeNode()
{
}
public virtual GeneralTreeNode<V> AddChild(V childValue)
{
var childNode = new GeneralTreeNode<V>(childValue, this);
Children.Add(childNode);
return childNode;
}
}
public class IntegratedValueNode : GeneralTreeNode<IntegratedValueNode>
{
public int whatever { get; set; }
public IntegratedValueNode()
{
Value = this;
}
}
default! 是 nullable reference types 的 null 状态抑制运算符。它让编译器接受这种初始化方式,但不会在运行时创建有效的 V 值。IntegratedValueNode 的构造函数执行完后,Value 才会指向当前实例。
为什么不能在 base(this) 中传入自身
创建派生类时,基类构造函数会先执行,此时派生类实例还没有完成构造,因此不能在基类构造函数调用中使用 this。另外,如果基类只有 GeneralTreeNode(V value, ...) 这个构造函数,派生类的无参构造函数就没有无参基类构造函数可供调用。
添加 protected GeneralTreeNode() 后,派生类可以延后初始化;外部代码仍不能通过无参构造函数创建一个尚未初始化的普通树节点。
初始化期间的限制
这种写法能保证 IntegratedValueNode 构造完成后 Value 已经赋值。在基类构造阶段以及派生类构造函数赋值之前,Value 暂时还是 default(V)。因此,这段时间不要调用会读取 Value 的虚方法,也不要在构造期间把当前实例交给其他代码。
如果必须保留原来的基类构造函数,也可以写成 IntegratedValueNode() : base(null!) { Value = this; }。不过,这会先把运行时的 null 传给非 nullable 参数,之后再通过赋值修正状态。通常,添加受保护的无参构造函数更清楚。
备注:内容仅供参考。
