原生C#枚举就像纸板剪影——只有标签,没有灵魂。需要为每种信用卡类型设置折扣率?准备好面对冗长的switch语句。想添加验证或本地化?你不得不在代码库中搜寻分散的辅助字典。
如果你认为原生枚举"够用",那只是因为还没遇到过需要将业务逻辑塞进巨型switch怪兽的需求场景。
想象一下:用类化的枚举实例取代单调的枚举——每个实例自带数据和行为。想要铂金卡折扣?直接问枚举对象。没有switch,没有魔法数字,没有代码异味。
这就是Milan强类型枚举模式的魔力——将枚举从哑常量升级为一等公民。
1 | 原生枚举的致命缺陷 | 缺陷 | 痛点 | |-------|-------| | 无行为 | 折扣、验证等逻辑需要外部工具类 | | 无扩展数据 | 无法附加显示名称等元数据 | | 难以演进 | 新增枚举值需更新所有switch否则引发bug | | 封装性差 | 逻辑散落各处,枚举无法自治 |
2 | 蓝图:Enumeration
public abstract class Enumeration<TEnum> : IEquatable<TEnum>
where TEnum : Enumeration<TEnum>
{
public int Value { get; }
public string Name { get; }
protected Enumeration(int value, string name) =>
(Value, Name) = (value, name);
public override string ToString() => Name;
public static IReadOnlyList<TEnum> List { get; }
static Enumeration()
{
List = typeof(TEnum)
.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.Where(f => f.FieldType == typeof(TEnum))
.Select(f => (TEnum)f.GetValue(null)!)
.ToList()
.AsReadOnly();
}
public static TEnum FromValue(int value) =>
List.Single(e => e.Value == value);
public static TEnum FromName(string name) =>
List.Single(e => string.Equals(e.Name, name, StringComparison.OrdinalIgnoreCase));
// 基于Value的相等性比较
public bool Equals(TEnum? other) => other is not null && Value == other.Value;
public override bool Equals(object? obj) => obj is TEnum other && Equals(other);
public override int GetHashCode() => Value;
}
3 | 案例:信用卡枚举类 3.1 基础枚举类
public abstract class CreditCard
: Enumeration<CreditCard>
{
protected CreditCard(int value, string name) : base(value, name) { }
public abstract decimal Discount { get; }
// 静态实例
public static readonly CreditCard Standard = new StandardCard();
public static readonly CreditCard Premium = new PremiumCard();
public static readonly CreditCard Platinum = new PlatinumCard();
// 私有子类封装行为
private sealed class StandardCard : CreditCard
{
public StandardCard() : base(1, "Standard") { }
public override decimal Discount => 0.01m;
}
private sealed class PremiumCard : CreditCard
{
public PremiumCard() : base(2, "Premium") { }
public override decimal Discount => 0.05m;
}
private sealed class PlatinumCard : CreditCard
{
public PlatinumCard() : base(3, "Platinum") { }
public override decimal Discount => 0.10m;
}
}
3.2 使用示例
var card = CreditCard.FromValue(1); // 标准卡
Console.WriteLine($"折扣率: {card.Discount:P}");
// → 折扣率: 1.00 %
var platinum = CreditCard.FromName("Platinum");
Console.WriteLine($"折扣率: {platinum.Discount:P}");
// → 折扣率: 10.00 %
无需switch语句,没有魔法数字,纯多态实现。
4 | 扩展超能力
| 功能 | 实现方式 |
|-------|-------|
| 附加属性 | 在基类添加如AnnualFee,各子类重写 |
| 复杂方法 | 添加virtual/abstract方法如CalculateRewardPoints |
| JSON支持 | 编写JsonConverter
5 | 测试变得简单
[Theory]
[InlineData(1, 0.01)]
[InlineData(3, 0.10)]
public void Discount_Should_Match_Value(int value, decimal expected)
{
var card = CreditCard.FromValue(value);
card.Discount.Should().Be(expected);
}
6 | 权衡与保障 | 考虑因素 | 缓解方案 | |-------|-------| | 反射开销 | 静态构造函数仅应用启动时运行一次 | | 开放集风险 | 标记静态实例为readonly,子类设为private sealed | | 序列化名称 | 使用Name或添加JsonName属性 | | 数据库映射 | 存储Value值,通过FromValue重建 |
🚀 收获