CSharp(三十八) 枚举(Enum)详解 —— 定义、使用与常用方法
一、什么是枚举?
枚举是一组有名字的整数常量,用来给一组相关的选项起一个有意义的名字。
打个比喻:
- 不用枚举时:
int direction = 1;—— 你只知道 1,但 1 是什么?上是 1 还是下是 1?得查文档。 - 用枚举后:
Direction direction = Direction.Up;—— 一眼就知道是"向上"!
枚举就是把"魔法数字"变成"有意义的名字",让代码可读、可维护、不容易写错。
二、定义枚举
用 enum 关键字定义:
// 基本定义
enum Direction
{
Up, // 默认 = 0
Down, // 默认 = 1
Left, // 默认 = 2
Right // 默认 = 3
}
默认规则:如果不指定值,第一个成员是 0,后面依次 +1。
2.1 自定义数值
enum DayOfWeek
{
Monday = 1, // 指定从 1 开始
Tuesday, // = 2
Wednesday, // = 3
Thursday, // = 4
Friday, // = 5
Saturday, // = 6
Sunday // = 7
}
2.2 任意指定数值(可以跳、可以不连续)
enum HttpStatus
{
OK = 200,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500
}
2.3 枚举的类型
枚举默认用 int 存储,也可以改成其他整数类型:
enum SmallEnum : byte // 用 byte 存储(0~255)
{
A, B, C
}
enum LargeEnum : long // 用 long 存储
{
One = 1,
Million = 1000000L
}
支持的底层类型:byte、sbyte、short、ushort、int、uint、long、ulong。
三、使用枚举
3.1 声明变量和赋值
// 声明
Direction dir;
// 赋值
dir = Direction.Up;
// 声明+赋值
DayOfWeek today = DayOfWeek.Monday;
Console.WriteLine(today); // 输出: Monday
3.2 比较枚举值
Direction dir = Direction.Left;
if (dir == Direction.Left)
{
Console.WriteLine("向左走!");
}
// switch 是枚举的最佳搭档
switch (dir)
{
case Direction.Up:
Console.WriteLine("向上");
break;
case Direction.Down:
Console.WriteLine("向下");
break;
case Direction.Left:
Console.WriteLine("向左");
break;
case Direction.Right:
Console.WriteLine("向右");
break;
}
3.3 作为方法参数
static void Move(Direction direction, int steps)
{
Console.WriteLine($"向{direction}方向移动{steps}步");
}
// 调用
Move(Direction.Up, 5); // 输出: 向Up方向移动5步
Move(Direction.Left, 3); // 输出: 向Left方向移动3步
四、枚举与整数互相转换
4.1 枚举 → 整数(强制转换)
DayOfWeek day = DayOfWeek.Friday;
int dayNumber = (int)day;
Console.WriteLine(dayNumber); // 输出: 5
4.2 整数 → 枚举(强制转换)
int value = 3;
DayOfWeek day = (DayOfWeek)value;
Console.WriteLine(day); // 输出: Thursday
注意:即使整数不在枚举定义的范围内,强制转换也不会报错!比如
(DayOfWeek)100能执行,但结果是未定义行为。
4.3 判断整数是否为有效的枚举值
int value = 100;
bool isValid = Enum.IsDefined(typeof(DayOfWeek), value);
Console.WriteLine(isValid); // 输出: False
五、枚举与字符串互相转换(常用!)
5.1 枚举 → 字符串
// 方式一:直接 ToString()
DayOfWeek day = DayOfWeek.Monday;
string dayStr = day.ToString();
Console.WriteLine(dayStr); // 输出: Monday
// 方式二:Enum.GetName()
string name = Enum.GetName(typeof(DayOfWeek), 3);
Console.WriteLine(name); // 输出: Thursday
5.2 字符串 → 枚举(Parse / TryParse)
// 方式一:Enum.Parse(可能抛异常!)
string input = "Friday";
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), input);
Console.WriteLine(day); // 输出: Friday
// 输入不存在的名字会抛异常:
// DayOfWeek bad = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), "NotADay"); // ❌ 抛异常!
// 方式二:Enum.TryParse(推荐!安全!)
Console.Write("请输入星期几: ");
string input = Console.ReadLine(); // 假设输入 "Monday"
if (Enum.TryParse<DayOfWeek>(input, out DayOfWeek day))
{
Console.WriteLine($"转换成功: {day}");
}
else
{
Console.WriteLine("无效的星期名,请输入 Monday~Sunday");
}
5.3 TryParse 忽略大小写
string input = "monday"; // 小写
// 默认是区分大小写的(根据版本不同行为可能不同,保险起见加参数)
if (Enum.TryParse(input, true, out DayOfWeek day)) // true = 忽略大小写
{
Console.WriteLine($"转换成功: {day}"); // 输出: Monday
}
六、枚举的常用方法大全
6.1 获取所有枚举值
// 获取所有枚举值
DayOfWeek[] allDays = (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek));
Console.WriteLine("一周的天数:");
foreach (DayOfWeek day in allDays)
{
Console.WriteLine($" {day} = {(int)day}");
}
输出:
一周的天数:
Monday = 1
Tuesday = 2
Wednesday = 3
Thursday = 4
Friday = 5
Saturday = 6
Sunday = 7
6.2 获取所有枚举名称
string[] names = Enum.GetNames(typeof(DayOfWeek));
Console.WriteLine("所有名称:");
foreach (string name in names)
{
Console.WriteLine(name);
}
输出:
所有名称:
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
6.3 判断值是否合法
int userInput = 10;
if (Enum.IsDefined(typeof(DayOfWeek), userInput))
{
Console.WriteLine($"{(DayOfWeek)userInput} 是有效的");
}
else
{
Console.WriteLine($"{userInput} 不是有效的星期值"); // 输出这句
}
6.4 获取枚举的底层类型
Type underlyingType = Enum.GetUnderlyingType(typeof(DayOfWeek));
Console.WriteLine(underlyingType); // 输出: System.Int32
七、完整示例:订单状态管理系统
using System;
// 定义订单状态
enum OrderStatus
{
Pending = 1, // 待处理
Confirmed, // 已确认 = 2
Shipping, // 配送中 = 3
Delivered, // 已送达 = 4
Cancelled = -1 // 已取消 = -1(可以负值)
}
class Program
{
// 处理方法:根据状态显示不同信息
static void ShowOrderInfo(OrderStatus status, string orderId)
{
Console.Write($"订单 {orderId} 状态: {status}");
switch (status)
{
case OrderStatus.Pending:
Console.Write(" → 请尽快处理");
break;
case OrderStatus.Confirmed:
Console.Write(" → 准备发货中");
break;
case OrderStatus.Shipping:
Console.Write(" → 正在配送");
break;
case OrderStatus.Delivered:
Console.Write(" → 已签收");
break;
case OrderStatus.Cancelled:
Console.Write(" → 订单已取消");
break;
}
Console.WriteLine();
}
// 返回所有状态的中文说明
static Dictionary<OrderStatus, string> GetStatusDescriptions()
{
var dict = new Dictionary<OrderStatus, string>();
foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus)))
{
dict[s] = s switch
{
OrderStatus.Pending => "待处理",
OrderStatus.Confirmed => "已确认",
OrderStatus.Shipping => "配送中",
OrderStatus.Delivered => "已送达",
OrderStatus.Cancelled => "已取消",
_ => "未知"
};
}
return dict;
}
static void Main()
{
// 1. 显示所有状态
Console.WriteLine("===== 所有订单状态 =====");
var descriptions = GetStatusDescriptions();
foreach (var kv in descriptions)
{
Console.WriteLine($" {kv.Key} (值={(int)kv.Key}): {kv.Value}");
}
// 2. 模拟处理订单
Console.WriteLine("\n===== 订单处理流程 =====");
// 用户输入状态值
Console.Write("请输入订单状态代码 (1-待处理,2-已确认,3-配送中,4-已送达,-1-取消): ");
string input = Console.ReadLine();
if (int.TryParse(input, out int statusCode)
&& Enum.IsDefined(typeof(OrderStatus), statusCode))
{
OrderStatus status = (OrderStatus)statusCode;
ShowOrderInfo(status, "ORD-2024001");
}
else
{
Console.WriteLine("无效的状态代码!");
}
// 3. 演示字符串解析
Console.WriteLine("\n===== 字符串解析 =====");
string[] testInputs = { "Delivered", "Cancelled", "Unknown" };
foreach (string test in testInputs)
{
if (Enum.TryParse(test, out OrderStatus result))
{
Console.WriteLine($"'{test}' 解析成功 → {result}");
}
else
{
Console.WriteLine($"'{test}' 解析失败!");
}
}
}
}
运行示例:
===== 所有订单状态 =====
Pending (值=1): 待处理
Confirmed (值=2): 已确认
Shipping (值=3): 配送中
Delivered (值=4): 已送达
Cancelled (值=-1): 已取消
===== 订单处理流程 =====
请输入订单状态代码: 3
订单 ORD-2024001 状态: Shipping → 正在配送
===== 字符串解析 =====
'Delivered' 解析成功 → Delivered
'Cancelled' 解析成功 → Cancelled
'Unknown' 解析失败!
八、高级特性:Flags 枚举(标志位枚举)
有时候一个东西可以同时拥有多个状态。比如文件权限:可读、可写、可执行可以同时存在。
8.1 定义 Flags 枚举
[Flags] // 加这个特性!
enum FilePermission
{
None = 0, // 无权限
Read = 1, // 可读 (二进制: 001)
Write = 2, // 可写 (二进制: 010)
Execute = 4, // 可执行 (二进制: 100)
FullControl = Read | Write | Execute // 全部权限 = 7
}
关键:每个值必须是 2 的幂次方(1、2、4、8、16...),这样可以用位运算组合。
8.2 使用 Flags 枚举
// 组合多个权限(用 | 运算)
FilePermission myPermission = FilePermission.Read | FilePermission.Write;
// 检查是否包含某个权限(用 HasFlag 或 & 运算)
if (myPermission.HasFlag(FilePermission.Read))
{
Console.WriteLine("有读权限");
}
if ((myPermission & FilePermission.Execute) == 0)
{
Console.WriteLine("无执行权限");
}
// 添加权限
myPermission |= FilePermission.Execute;
Console.WriteLine($"添加执行权限后: {myPermission}"); // Read, Write, Execute
// 移除权限
myPermission &= ~FilePermission.Write;
Console.WriteLine($"移除写权限后: {myPermission}"); // Read, Execute
输出:
有读权限
无执行权限
添加执行权限后: Read, Write, Execute
移除写权限后: Read, Execute
8.3 完整示例:权限检查系统
using System;
[Flags]
enum Permission
{
None = 0,
Create = 1 << 0, // 1 (这种写法更专业)
Read = 1 << 1, // 2
Update = 1 << 2, // 4
Delete = 1 << 3, // 8
Admin = Create | Read | Update | Delete // 15
}
class Program
{
static void CheckPermission(Permission userPerm, Permission required)
{
Console.Write($"用户权限: {userPerm,-20} 需要: {required,-10}");
if (userPerm.HasFlag(required))
{
Console.WriteLine(" ✅ 允许");
}
else
{
Console.WriteLine(" ❌ 拒绝");
}
}
static void Main()
{
// 定义几个角色
Permission guest = Permission.Read;
Permission editor = Permission.Read | Permission.Update | Permission.Create;
Permission admin = Permission.Admin;
Console.WriteLine($"游客权限: {guest} (值={(int)guest})");
Console.WriteLine($"编辑权限: {editor} (值={(int)editor})");
Console.WriteLine($"管理员权限: {admin} (值={(int)admin})");
Console.WriteLine();
// 检查各角色能否删除
CheckPermission(guest, Permission.Delete);
CheckPermission(editor, Permission.Delete);
CheckPermission(admin, Permission.Delete);
Console.WriteLine();
// 检查各角色能否读取
CheckPermission(guest, Permission.Read);
CheckPermission(editor, Permission.Read);
CheckPermission(admin, Permission.Read);
}
}
输出:
游客权限: Read (值=2)
编辑权限: Create, Read, Update (值=7)
管理员权限: Admin (值=15)
用户权限: Read 需要: Delete ❌ 拒绝
用户权限: Create, Read, Update 需要: Delete ❌ 拒绝
用户权限: Admin 需要: Delete ✅ 允许
用户权限: Read 需要: Read ✅ 允许
用户权限: Create, Read, Update 需要: Read ✅ 允许
用户权限: Admin 需要: Read ✅ 允许
九、枚举的常用技巧
9.1 给枚举加描述信息(Description 特性)
using System.ComponentModel;
enum OrderStatus
{
[Description("待处理")]
Pending = 1,
[Description("已确认")]
Confirmed,
[Description("配送中")]
Shipping,
[Description("已送达")]
Delivered,
[Description("已取消")]
Cancelled = -1
}
// 扩展方法:获取描述文字
static class EnumExtensions
{
public static string GetDescription(this Enum value)
{
var field = value.GetType().GetField(value.ToString());
var attr = (DescriptionAttribute)Attribute.GetCustomAttribute(
field, typeof(DescriptionAttribute));
return attr?.Description ?? value.ToString();
}
}
// 使用
OrderStatus status = OrderStatus.Shipping;
Console.WriteLine(status.GetDescription()); // 输出: 配送中
9.2 枚举遍历并显示名称和值
Console.WriteLine("{0,-20} {1,-10}", "名称", "值");
Console.WriteLine(new string('-', 30));
foreach (OrderStatus status in Enum.GetValues(typeof(OrderStatus)))
{
Console.WriteLine("{0,-20} {1,-10}", status, (int)status);
}
输出:
名称 值
------------------------------
Pending 1
Confirmed 2
Shipping 3
Delivered 4
Cancelled -1
9.3 从用户输入安全地获取枚举值
static OrderStatus? GetOrderStatusFromUser()
{
Console.WriteLine("请选择订单状态:");
// 列出所有可用选项
foreach (OrderStatus status in Enum.GetValues(typeof(OrderStatus)))
{
Console.WriteLine($" {(int)status} - {status}");
}
Console.Write("请输入数字: ");
string input = Console.ReadLine();
if (int.TryParse(input, out int code) && Enum.IsDefined(typeof(OrderStatus), code))
{
return (OrderStatus)code;
}
Console.WriteLine("无效输入!");
return null;
}
十、常见易错点(避坑指南)
坑1:0 值建议定义
// ❌ 不推荐:没有 0 值
enum Status { Active = 1, Inactive = 2 }
// 未初始化的 Status 变量默认是 0,但 0 不是有效值!
Status s = new Status();
Console.WriteLine(s); // 输出: 0 ← 一个不存在于定义中的值!
// ✅ 推荐:始终定义 0 值
enum Status
{
Unknown = 0, // 明确的默认值
Active = 1,
Inactive = 2
}
坑2:Parse 不存在的名字会抛异常
// ❌ 危险
string input = "Hackday";
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), input); // 抛异常!
// ✅ 安全
if (Enum.TryParse(input, out DayOfWeek safeDay))
{
Console.WriteLine($"解析成功: {safeDay}");
}
else
{
Console.WriteLine("解析失败");
}
坑3:强制转换不检查合法性
DayOfWeek day = (DayOfWeek)999; // 不会报错!
Console.WriteLine(day); // 输出: 999 ← 诡异的值
// 正确的做法:先检查
int value = 999;
if (Enum.IsDefined(typeof(DayOfWeek), value))
{
DayOfWeek safe = (DayOfWeek)value;
Console.WriteLine(safe);
}
else
{
Console.WriteLine("无效值");
}
十一、总结
常用方法速查表
| 操作 | 代码 |
|---|---|
| 定义 | enum Name { A, B, C } |
| 赋值 | Name x = Name.A; |
| 转整数 | int n = (int)x; |
| 转枚举 | Name x = (Name)2; |
| 转字符串 | x.ToString() |
| 字符串转枚举(安全) | Enum.TryParse("A", out Name x) |
| 获取所有值 | Enum.GetValues(typeof(Name)) |
| 获取所有名称 | Enum.GetNames(typeof(Name)) |
| 检查是否合法 | Enum.IsDefined(typeof(Name), value) |
| Flags 标志位 | [Flags] + 2的幂次方值 |
| 组合权限 | perm = A | B |
| 检查权限 | perm.HasFlag(A) |
| 移除权限 | perm &= ~A |
记忆口诀
| 场景 | 口诀 |
|---|---|
| 定义 | enum 起名花括号,成员逗号分隔开 |
| 默认值 | 不写值从 0 起,后面自动加上 1 |
| 声明 | 类型名加点来取值,比如 Status.Active |
| 转整数 | 前面加个括号强转,(int)myEnum |
| 转字符串 | 直接 .ToString() 就行 |
| 字符串转回来 | Enum.TryParse 最安全 |
| Flags | 加 [Flags],值用 1,2,4,8,组合用 | |
一句话总结:枚举就是把"魔法数字"变成"有名字的选项",让代码更好读、更不容易出错。最常用的套路是:用
switch处理不同枚举值,用Enum.TryParse从字符串安全转换,用HasFlag检查 Flags 组合枚举。