C# 中的枚举(Enum)学习笔记

1. 基础概念与用法

1.1 什么是枚举(Enum)?

枚举(Enum) 是 C# 中一种特殊的值类型,用于定义一组命名的常量。它提供了一种更清晰、更安全的方式来表示一组有限的、固定的选项。

例如:

  • 一周的星期:MondayTuesday
  • 用户状态:ActiveInactivePending
  • 颜色类型:RedGreenBlue

使用枚举可以让代码更具可读性减少错误(避免使用魔法数字或字符串),并提升类型安全性


1.2 如何定义枚举?

使用 enum 关键字定义:

enum Weekday
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

默认情况下,枚举成员从 0 开始递增(Monday = 0, Tuesday = 1, …)。

你也可以显式指定值

enum Status
{
    Pending = 0,
    Approved = 1,
    Rejected = 2,
    Archived = 100  // 可以跳过数字
}

还可以指定底层类型(默认是 int):

enum SmallStatus : byte
{
    Off = 0,
    On = 1
}

支持的底层类型包括:bytesbyteshortushortintuintlongulong


1.3 基本使用示例

声明与赋值

Weekday today = Weekday.Friday;
Console.WriteLine(today); // 输出:Friday

转换为整数或从整数转换

int dayNumber = (int)Weekday.Wednesday; // 2
Weekday day = (Weekday)3; // Thursday

⚠️ 注意:强制转换可能产生无效枚举值(如 (Weekday)999),需谨慎。


1.4 常用方法与操作

1. 获取所有枚举值(Enum.GetValues

foreach (Weekday day in Enum.GetValues(typeof(Weekday)))
{
    Console.WriteLine(day);
}

2. 获取所有名称(Enum.GetNames

string[] names = Enum.GetNames(typeof(Weekday));
foreach (string name in names)
{
    Console.WriteLine(name);
}

3. 将字符串转为枚举(Enum.Parse / Enum.TryParse

string input = "Friday";
Weekday day = (Weekday)Enum.Parse(typeof(Weekday), input);

// 更安全的方式:使用 TryParse
if (Enum.TryParse(input, out Weekday result))
{
    Console.WriteLine($"解析成功: {result}");
}
else
{
    Console.WriteLine("无效的枚举名称");
}

建议使用 TryParse,避免因无效字符串抛出异常。


2. 进阶知识点

2.1 枚举支持“位标志”(Flags)

当你需要组合多个选项时(如文件权限:读 + 写 + 执行),可以使用 [Flags] 特性。

定义方式

[Flags]
enum FileAccess
{
    None = 0,
    Read = 1,        // 0001
    Write = 2,       // 0010
    Execute = 4,     // 0100
    ReadWrite = Read | Write // 0011 = 3
}

💡 成员值必须是 2 的幂次(1, 2, 4, 8, 16...),以便进行位运算。

使用示例

FileAccess access = FileAccess.Read | FileAccess.Write;
Console.WriteLine(access); // 输出:ReadWrite

// 检查是否包含某个权限
bool canRead = access.HasFlag(FileAccess.Read); // true

⚠️ HasFlag 性能较低,对性能敏感场景建议用位运算:(access & FileAccess.Read) != 0


2.2 枚举与字符串的友好显示(使用 DescriptionAttribute

默认 ToString() 返回枚举名称,但有时你想显示更友好的中文或描述。

示例:带描述的枚举

using System.ComponentModel;

enum OrderStatus
{
    [Description("待支付")]
    Pending,

    [Description("已发货")]
    Shipped,

    [Description("已完成")]
    Completed
}

获取描述的辅助方法

public static string GetDescription(Enum value)
{
    var field = value.GetType().GetField(value.ToString());
    var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(
        field, typeof(DescriptionAttribute));
    return attribute?.Description ?? value.ToString();
}

使用:

Console.WriteLine(GetDescription(OrderStatus.Shipped)); // 输出:已发货

2.3 枚举的局限性

  • 枚举不能继承(隐式继承 System.Enum,而 System.Enum 是抽象类)。
  • 枚举不能定义方法、属性或构造函数(C# 11 之前;C# 11 起支持枚举上的扩展成员,但功能仍有限)。
  • 枚举不是类型安全的到极致:你可以强制转换任意整数为枚举,即使它不在定义中。

🛡️ 最佳实践:在关键逻辑中,使用 Enum.IsDefined 验证值是否合法。

if (Enum.IsDefined(typeof(Weekday), 999))
{
    // 不会进入这里
}

2.4 C# 11+ 新特性:枚举上的静态抽象成员(预览/有限支持)

虽然目前仍不能在枚举中直接写方法,但可通过扩展方法泛型约束模拟行为。

例如,为所有枚举添加 IsValid 扩展:

public static class EnumExtensions
{
    public static bool IsValid<T>(this T value) where T : struct, Enum
    {
        return Enum.IsDefined(typeof(T), value);
    }
}

// 使用
Weekday day = (Weekday)999;
Console.WriteLine(day.IsValid()); // False

3. 实际工作中的使用场景

场景 1:表示状态机(State Machine)

解释:在业务流程中(如订单、审批、任务),对象常处于不同状态。使用枚举可以清晰定义状态流转。

enum OrderState
{
    Created,
    Paid,
    Shipped,
    Delivered,
    Cancelled
}

class Order
{
    public OrderState State { get; set; }

    public void Ship()
    {
        if (State == OrderState.Paid)
            State = OrderState.Shipped;
        else
            throw new InvalidOperationException("只有已支付的订单才能发货!");
    }
}

优点:防止非法状态转换,代码可读性强。


场景 2:配置选项或设置项

解释:程序中常有“模式”选择,如日志级别、环境类型、加密方式等。

enum LogLevel
{
    Debug,
    Info,
    Warning,
    Error
}

void Log(LogLevel level, string message)
{
    if (level >= LogLevel.Warning)
    {
        Console.WriteLine($"[{level}] {message}");
    }
}

优点:比字符串更安全,避免拼写错误(如 "warnning")。


场景 3:权限/角色控制(配合 [Flags]

解释:用户权限常是多个选项的组合(如“可读+可写”),[Flags] 枚举天然适合此场景。

[Flags]
enum UserPermission
{
    None = 0,
    View = 1,
    Edit = 2,
    Delete = 4,
    Admin = View | Edit | Delete
}

UserPermission userPerm = UserPermission.View | UserPermission.Edit;

if (userPerm.HasFlag(UserPermission.Edit))
{
    // 允许编辑
}

优点:节省存储空间(一个整数表示多个权限),逻辑清晰。


场景 4:API 请求参数标准化

解释:在 Web API 中,前端传入的参数(如 sort=asc)可映射为枚举,避免字符串比较错误。

enum SortDirection
{
    Asc,
    Desc
}

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{
    [HttpGet]
    public IActionResult Get([FromQuery] SortDirection sort = SortDirection.Asc)
    {
        // 根据 sort 执行不同排序逻辑
        return Ok();
    }
}

优点:自动验证输入合法性,Swagger 等工具能自动生成文档选项。


场景 5:替代“魔法数字”或“魔法字符串”

解释:避免代码中出现 if (status == 2) 这种难以理解的写法。

❌ 不推荐:

if (user.Status == 1) { ... } // 1 是什么意思?

✅ 推荐:

if (user.Status == UserStatus.Active) { ... } // 一目了然

总结

项目 说明
本质 命名的整型常量集合
优点 提高可读性、类型安全、避免魔法值
常用操作 Parse / TryParseGetValuesHasFlag
进阶技巧 [Flags] 位枚举、Description 友好显示、扩展方法
典型场景 状态管理、权限控制、配置选项、API 参数

🌟 给初学者的建议

  • 优先使用枚举代替 intstring 表示固定选项。
  • 组合选项时记得加 [Flags] 并使用 2 的幂次赋值。
  • 始终用 TryParse 而非 Parse 处理用户输入。
  • 不要滥用:选项超过 20 个或频繁变化时,考虑用数据库表或配置文件。
posted @ 2025-11-03 16:38  恨水长秋  阅读(175)  评论(0)    收藏  举报