Skip to content

08a-枚举(Enum)

枚举是一组命名常量,用于表示一组有限的可选值。枚举是值类型,底层默认使用 int 存储。


一、基本定义和使用

csharp
// 基本定义
public enum Season { Spring, Summer, Autumn, Winter }

// 使用
Season current = Season.Summer;
Console.WriteLine(current);        // Summer
Console.WriteLine((int)current);   // 1(默认从 0 开始)

// 遍历所有枚举值
foreach (Season s in Enum.GetValues<Season>())
    Console.WriteLine($"{s} = {(int)s}");
// Spring = 0, Summer = 1, Autumn = 2, Winter = 3

指定底层类型和值

csharp
// 指定底层类型(默认 int)
public enum ErrorCode : byte { None, NotFound, Timeout }

// 手动指定值
public enum HttpStatus
{
    OK = 200,
    Created = 201,
    BadRequest = 400,
    NotFound = 404,
    ServerError = 500
}

// 多个成员可共享同一值(互为别名)
public enum Days
{
    Sunday = 0,
    Monday = 1,
    Tuesday = 2,
    Wednesday = 3,
    Thursday = 4,
    Friday = 5,
    Saturday = 6,
    Weekend = 0  // 与 Sunday 同值
}

支持的底层类型

类型大小说明
byte / sbyte1 字节小范围枚举
short / ushort2 字节中等范围
int / uint4 字节默认,最常用
long / ulong8 字节大范围枚举

二、Flags 枚举

使用 [Flags] 特性创建位掩码枚举,多个值可通过位运算组合。

csharp
[Flags]
public enum FileAccess
{
    None    = 0,      // 000
    Read    = 1,      // 001
    Write   = 2,      // 010
    Execute = 4,      // 100
    // 组合值
    ReadWrite = Read | Write  // 011 = 3
}

// 位运算组合
FileAccess permission = FileAccess.Read | FileAccess.Write;

// 检查是否包含某权限
bool canRead = (permission & FileAccess.Read) != 0;    // true
bool canExecute = permission.HasFlag(FileAccess.Execute); // false

// 添加权限
permission |= FileAccess.Execute;  // 现在包含 Execute

// 移除权限
permission &= ~FileAccess.Write;   // 移除 Write

// 切换权限
permission ^= FileAccess.Read;     // 移除 Read(原来有)
permission ^= FileAccess.Read;     // 添加 Read(原来没有,来回切换)

// 输出:Read, Execute(逗号分隔)
Console.WriteLine(permission);

Flags 枚举设计规范

csharp
[Flags]
public enum Permissions
{
    None        = 0,    // 必须为 0
    View        = 1,    // 2^0
    Create      = 2,    // 2^1
    Edit        = 4,    // 2^2
    Delete      = 8,    // 2^3
    Admin       = View | Create | Edit | Delete  // 组合
}

三、枚举的常用操作

1. 字符串 ↔ 枚举

csharp
// 枚举 → 字符串
string name = Season.Summer.ToString();  // "Summer"
string name2 = Enum.GetName(Season.Summer); // "Summer"

// 字符串 → 枚举(Parse)
Season s = (Season)Enum.Parse(typeof(Season), "Summer");   // Summer
Season s2 = Enum.Parse<Season>("Summer");  // 泛型版本(C# 7.0+)

// 字符串 → 枚举(TryParse,推荐)
if (Enum.TryParse<Season>("Summer", out Season result))
    Console.WriteLine(result);  // Summer

// 忽略大小写
if (Enum.TryParse<Season>("summer", ignoreCase: true, out result))
    Console.WriteLine(result);  // Summer

2. 枚举 ↔ 数值

csharp
// 枚举 → 数值
int val = (int)Season.Summer;  // 1

// 数值 → 枚举
Season s = (Season)1;  // Summer
Season s2 = (Season)999;  // 不报错!即使没有对应值

// 安全判断:值是否合法
bool defined = Enum.IsDefined(typeof(Season), 1));    // true
bool defined2 = Enum.IsDefined(typeof(Season), 999)); // false

3. 遍历枚举

csharp
// 获取所有枚举值数组
Season[] all = Enum.GetValues<Season>();

// 获取所有枚举名称
string[] names = Enum.GetNames<Season>();  // ["Spring","Summer","Autumn","Winter"]

// 遍历
foreach (Season s in Enum.GetValues<Season>())
    Console.WriteLine($"{(int)s}: {s}");

// 获取成员数量
int count = Enum.GetNames<Season>().Length;  // 4

四、枚举与 switch 配合

csharp
// 传统 switch 语句
string GetSeasonMessage(Season season)
{
    switch (season)
    {
        case Season.Spring:
            return "春暖花开";
        case Season.Summer:
            return "夏日炎炎";
        case Season.Autumn:
            return "秋高气爽";
        case Season.Winter:
            return "冬日暖阳";
        default:
            return "未知季节";
    }
}

// switch 表达式(更简洁)
string GetSeasonMessage2(Season season) => season switch
{
    Season.Spring => "春暖花开",
    Season.Summer => "夏日炎炎",
    Season.Autumn => "秋高气爽",
    Season.Winter => "冬日暖阳",
    _ => "未知季节"
};

注意: switch 处理枚举时最好包含所有成员 + default 分支,以适应未来新增枚举值的情况。


五、枚举的常用方法

方法说明示例结果
Enum.GetValues<T>()获取所有枚举值数组GetValues<Season>()[Spring, Summer, Autumn, Winter]
Enum.GetNames<T>()获取所有枚举名称数组GetNames<Season>()["Spring",...]
Enum.Parse<T>(string)字符串转枚举(抛异常)Parse<Season>("Summer")Summer
Enum.TryParse<T>(...)字符串转枚举(安全)TryParse("summer", out s)true, s=Summer
Enum.IsDefined<T>(value)检查值是否合法IsDefined<Season>(1)true
Enum.GetName<T>(value)获取值的名称GetName(Season.Summer)"Summer"
HasFlag(flag)检查是否包含标志(Flags)perm.HasFlag(Read)true/false
ToString()枚举转字符串Summer.ToString()"Summer"

六、枚举 vs 其他方案

场景推荐方案原因
有限固定可选值enum类型安全,可读性好
需要位运算组合[Flags] enum支持 &、管道符等位操作
值来自外部(数据库/API)conststatic readonly枚举要求编译时确定
需要扩展新值(不修改源码)enum + switch default但更好的方案是策略模式
大量动态值Dictionary<string, int>枚举值应有限且固定

七、综合案例:订单状态

csharp
public enum OrderStatus
{
    Pending = 0,
    Paid = 1,
    Shipped = 2,
    Delivered = 3,
    Cancelled = 4,
    Refunded = 5
}

public class Order
{
    public string Id { get; set; }
    public OrderStatus Status { get; set; }
    public decimal Amount { get; set; }

    public string GetStatusDescription() => Status switch
    {
        OrderStatus.Pending   => "待支付",
        OrderStatus.Paid      => "已支付,待发货",
        OrderStatus.Shipped   => "已发货",
        OrderStatus.Delivered => "已送达",
        OrderStatus.Cancelled => "已取消",
        OrderStatus.Refunded  => "已退款",
        _ => "未知状态"
    };

    public bool CanCancel() => Status switch
    {
        OrderStatus.Pending or OrderStatus.Paid => true,
        _ => false
    };

    public void ProcessNext()
    {
        Status = Status switch
        {
            OrderStatus.Pending => OrderStatus.Paid,
            OrderStatus.Paid => OrderStatus.Shipped,
            OrderStatus.Shipped => OrderStatus.Delivered,
            _ => Status
        };
    }
}

核心知识点总结

枚举关键特性

特性说明
值类型继承自 System.EnumSystem.ValueType
底层存储默认为 int,可指定 byte/short/long
默认值第一个成员为 0(未显式赋值时)
类型安全不能将其他枚举类型隐式赋值给它
编译时确定值在编译时固定,不能运行时新增

注意事项

  1. 枚举默认从 0 开始——未赋值时第一个成员为 0,后续依次递增
  2. 显示转换不会检查合法性——(Season)999 不会报错,用 Enum.IsDefined() 检查
  3. Flags 枚举需要 2 的幂——1, 2, 4, 8...,且必须包含 None = 0
  4. HasFlag 性能低于位运算——(perm & Flag) != 0perm.HasFlag(Flag)
  5. Enum.Parse 会抛异常——不确定来源的字符串用 Enum.TryParse
  6. switch 必须包含所有情况——加上 default 防止新增枚举值后遗漏
  7. 避免将枚举用于开放集合——枚举值应当固定且有限
  8. 命名用单数——普通枚举用单数(Season),Flags 枚举用复数(FileAccess

Released under the MIT License.