C#通过父类ObjectA引用赋值ObjectB独有字段是否必须做类型检查
除了强制类型转换外,有3种更符合开闭原则、可维护性更强的实现方案:
方案1:接口抽象(最常用,性价比最高)
给所有需要接收time参数的类型定义公共接口,不需要修改原有父类ObjectA的逻辑,后续新增需要时间参数的子类只需实现接口即可,适配性极强。
// 定义公共接口 public interface ITimeSettable { void SetTime(float time); } // 让ObjectB实现接口 public class ObjectB : ObjectA, ITimeSettable { public float time; public void SetTime(float time) => this.time = time; public override void StuffToDo() { // 使用time的业务逻辑 } } // 修改ObjectX的PassTimeToObjectB方法 public void PassTimeToObjectB() { if (_objectAOrBStoredInAReference is ITimeSettable timeSettable) { timeSettable.SetTime(time); } }
这种方案的判断逻辑没有和具体子类绑定,后续新增ObjectD等需要接收time的子类只要实现ITimeSettable即可,不需要修改ObjectX的现有代码。
方案2:访问者模式(适合类层次结构稳定的场景)
如果你当前的ObjectA子类数量已经固定,后续不会频繁新增子类,可以用访问者模式实现完全类型安全的操作分发,完全不需要任何运行时类型判断。
// 定义访问者接口 public interface IObjectAVisitor { void Visit(ObjectB b); void Visit(ObjectC c); // 其他子类对应添加Visit方法 } // 修改父类ObjectA,新增Accept方法 public abstract class ObjectA { public abstract void StuffToDo(); public abstract void Accept(IObjectAVisitor visitor); } // ObjectB实现Accept方法 public class ObjectB : ObjectA { public float time; public void SetTime(float time) => this.time = time; public override void StuffToDo() { /* 业务逻辑 */ } public override void Accept(IObjectAVisitor visitor) => visitor.Visit(this); } // ObjectC实现Accept方法 public class ObjectC : ObjectA { public override void StuffToDo() { /* 业务逻辑 */ } public override void Accept(IObjectAVisitor visitor) => visitor.Visit(this); } // 修改ObjectX,实现访问者接口 public class ObjectX : IObjectAVisitor { public float time; private ObjectA _objectAOrBStoredInAReference; public ObjectX(ObjectA objectAOrB) { _objectAOrBStoredInAReference = objectAOrB; } public void PassTimeToObjectB() { _objectAOrBStoredInAReference.Accept(this); } public void SomeStuffToDo() { PassTimeToObjectB(); _objectAOrBStoredInAReference.StuffToDo(); } // 实现访问者接口的Visit方法 void IObjectAVisitor.Visit(ObjectB b) { b.SetTime(time); } void IObjectAVisitor.Visit(ObjectC c) { // ObjectC不需要time,空实现即可 } }
这种方案所有类型校验都在编译期完成,不会出现运行时类型转换异常。
方案3:构造时注入委托(适合创建ObjectX时就明确传入对象类型的场景)
如果ObjectX实例创建的时候,你已经明确知道传入的是ObjectB还是其他子类,可以在构造阶段直接把设置time的逻辑注入,运行时完全不需要任何重复的类型校验。
public class ObjectX { public float time; private ObjectA _objectAOrBStoredInAReference; private Action<float> _setTimeAction; public ObjectX(ObjectA objectAOrB) { _objectAOrBStoredInAReference = objectAOrB; // 构造时一次性判断,后续不用重复执行 if (objectAOrB is ObjectB b) { _setTimeAction = t => b.SetTime(t); } // 其他子类有需要的也可以对应加逻辑 } public void PassTimeToObjectB() { _setTimeAction?.Invoke(time); } // 其余代码保持不变 }
这种方案的类型判断只在构造时执行一次,后续调用PassTimeToObjectB的时候没有任何额外开销,性能最优。
内容的提问来源于stack exchange,提问作者rootpanthera
相关产品推荐
相关产品推荐

