C#中如何实现根据枚举参数动态限定类的可用属性?
原实现的核心问题
原设计采用平铺式属性结构,将不同取值规则下的专属属性全部放置在同一个类中,没有建立属性和类型的绑定关系,既容易产生类型不匹配的无效数据(比如Range类型下给Presets赋值),也无法从根本上阻止调用方访问不属于当前类型的属性。
最优实现:基于继承的可辨识结构设计
C#没有原生的可辨识联合(Discriminated Union)语法,可以通过抽象基类+派生类的方式实现同等效果,从编译期就约束属性的合法访问范围,满足“特定类型下才存在对应属性”的需求。
实现步骤
- 提取所有类型共有的基础属性到抽象基类
public enum ValueType { Range, Preset } public abstract class Register { // 所有寄存器通用的基础属性 public string Name { get; set; } public string Unit { get; set; } public byte ID { get; set; } public string Address { get; set; } // 保留原有的通用方法 public ushort GetAddrAsShort() { return Convert.ToUInt16(Address, 16); } // 保留原有类型标识能力,由派生类固定返回对应值 public abstract ValueType RegisterType { get; } }
- 为两种取值规则分别创建派生类,每个类只封装自身合法存在的专属属性
- Range类型寄存器:
public class RangeRegister : Register { // 仅Range类型可访问的取值范围属性 public byte MaxValue { get; set; } public byte MinValue { get; set; } public override ValueType RegisterType => ValueType.Range; }
- Preset类型寄存器:
public class PresetRegister : Register { // 仅Preset类型可访问的预设值字典 public Dictionary<byte, string> Presets { get; set; } public override ValueType RegisterType => ValueType.Preset; }
方案优势
- 编译期强约束:要访问
MaxValue、MinValue,必须先将对象显式转换为RangeRegister类型;要访问Presets,必须转换为PresetRegister类型,从语法层面彻底杜绝跨类型访问属性的逻辑错误 - 无冗余内存开销:每个类只存储自身需要的字段,不会出现非对应类型下的属性冗余占位
- 扩展灵活:后续新增取值规则类型时,只需新增对应派生类即可,无需修改原有核心类的代码
兼容旧代码的过渡方案
如果暂时无法重构原有调用逻辑,可在原类结构的属性访问逻辑中加入合法性校验,运行时拦截非法访问:
class Register { public string Name { get; set; } public string Unit { get; set; } public byte ID { get; set; } public ValueType type { get; set; } public string Address { get; set; } // 私有字段存储专属属性值,不直接对外暴露 private byte _maxValue; private byte _minValue; private Dictionary<byte, string> _presets; public byte MaxValue { get => type == ValueType.Range ? _maxValue : throw new InvalidOperationException("当前为Preset类型寄存器,不存在MaxValue属性"); set { if (type != ValueType.Range) throw new InvalidOperationException("仅Range类型寄存器支持设置MaxValue"); _maxValue = value; } } public byte MinValue { get => type == ValueType.Range ? _minValue : throw new InvalidOperationException("当前为Preset类型寄存器,不存在MinValue属性"); set { if (type != ValueType.Range) throw new InvalidOperationException("仅Range类型寄存器支持设置MinValue"); _minValue = value; } } public Dictionary<byte, string> Presets { get => type == ValueType.Preset ? _presets : throw new InvalidOperationException("当前为Range类型寄存器,不存在Presets属性"); set { if (type != ValueType.Preset) throw new InvalidOperationException("仅Preset类型寄存器支持设置Presets"); _presets = value; } } public ushort GetAddrAsShort() { return Convert.ToUInt16(Address, 16); } } enum ValueType { Range, Preset }
该方案的合法性校验为运行时检查,错误只有在代码运行到对应逻辑时才会暴露,可靠性弱于编译期约束的派生类方案,仅适合短期兼容旧代码场景使用。
内容的提问来源于stack exchange,提问作者Jacob Petersen
相关产品推荐
相关产品推荐

