//enum = 枚举;struct=结构体;=>值类型
//class=类/类型;interface=接口;delegate=委托;=>引用类型
通俗解释:枚举(Enum)—— “选择题的固定选项”
1. 核心比喻:点菜单
普通变量 → 服务员问”想喝什么?”(你可以回答任意饮料名,甚至乱说”墨水”)。
枚举类型 → 菜单上只有咖啡、茶、牛奶三个选项(只能选其中之一)。
示例:
enum 饮料 { 咖啡, 茶, 牛奶 } // 定义枚举
饮料 我的选择 = 饮料.茶; // ✅ 合法
// 我的选择 = "可乐"; ❌ 编译错误!不在选项内
2. 枚举的关键特点
| 特性 | 说明 |
|---|---|
| 固定选项 | 提前定义好所有可能值,像单选题的选项 |
| 本质是整数 | 默认咖啡=0、茶=1、牛奶=2(可自定义数字) |
| 增强可读性 | 比直接用数字(如int 饮料 = 1)更直观 |
3. 实际应用场景
场景1:游戏角色状态
enum 角色状态 { 待机, 行走, 攻击, 死亡 }
角色状态 当前状态 = 角色状态.攻击;
if (当前状态 == 角色状态.死亡)
{
Console.WriteLine("显示阵亡特效");
}
场景2:错误码分类
enum 错误码
{
成功 = 200,
未找到 = 404,
服务器错误 = 500
}
void 处理错误(错误码 code)
{
if (code == 错误码.未找到) Console.WriteLine("检查URL地址");
}
场景3:每周工作日
enum 星期 { 周一, 周二, 周三, 周四, 周五, 周六, 周日 }
bool 是否工作日(星期 天)
=> 天 != 星期.周六 && 天 != 星期.周日;
4. 自定义枚举值
enum 权限级别
{
游客 = 1, // 手动赋值
会员 = 3,
管理员 = 9
}
int 管理员代码 = (int)权限级别.管理员; // 获取值 → 9
权限级别 我的权限 = (权限级别)3; // 数字转枚举 → 会员
5. 枚举进阶技巧
(1) 组合枚举(位标志)
[Flags] // 添加特性
enum 技能
{
无 = 0,
火系 = 1,
冰系 = 2,
雷系 = 4,
全系 = 火系 | 冰系 | 雷系
}
技能 我的技能 = 技能.火系 | 技能.雷系;
Console.WriteLine(我的技能); // 输出:"火系, 雷系"
(2) 遍历枚举
foreach (星期 天 in Enum.GetValues(typeof(星期)))
{
Console.WriteLine($"今天是{天}");
}
(3) 字符串转枚举
权限级别 权限 = Enum.Parse<权限级别>("管理员");
6. 枚举 vs 常量
| 对比项 | 枚举(enum) |
常量(const) |
|---|---|---|
| 组织方式 | 把相关选项归类在一起 | 分散的独立值 |
| 类型安全 | 限制只能选指定值 | 仍是普通变量,可能被误赋值 |
| 可读性 | 状态.运行中 > const int 状态 = 1 |
适合独立的不相关值(如MAX_SIZE) |
总结口诀
🔹 枚举就像选择题,选项固定防手误
🔹 默认从零排编号,自定义值更自由
🔹 位标志[Flags]组合妙,|操作权限高
🔹 比魔数(如1/2)更易懂,代码清晰bug少
就像交通信号灯:
常量 → 用1=红、2=绿、3=黄(容易记混)
枚举 →信号灯.红、信号灯.绿(一目了然)
黄金法则:
当有一组相关的固定值时优先用枚举
需要二进制组合时加[Flags]
避免用枚举替代复杂状态机(如超过10个状态)
通俗解释:枚举类型的基类 —— “枚举的老祖宗”
1. 核心结论
所有枚举类型(enum)都隐式继承自System.Enum类。
但System.Enum本身不是class,而是一个特殊的结构体类型(abstract class Enum),由CLR(.NET运行时)直接支持。
📌 记住:
enum➔System.Enum(基类) ➔System.ValueType(值类型的基类) ➔System.Object(万物之源)
2. 为什么需要知道基类?
因为System.Enum提供了枚举的通用操作,比如:
获取枚举值列表:Enum.GetValues(typeof(你的枚举))
字符串转枚举:Enum.Parse<你的枚举>("枚举值名字")
检查值是否合法:Enum.IsDefined(typeof(你的枚举), 值)
示例:
enum 颜色 { 红, 绿, 蓝 }
// 1. 遍历所有枚举值
foreach (颜色 c in Enum.GetValues(typeof(颜色)))
{
Console.WriteLine(c); // 输出:红、绿、蓝
}
// 2. 字符串转枚举
var 我的颜色 = Enum.Parse<颜色>("绿"); // 得到 颜色.绿
// 3. 检查值是否合法
bool 合法 = Enum.IsDefined(typeof(颜色), 5); // false(因为没有定义5对应的值)
3. 底层原理(通俗版)
enum本质是整数(默认int,但可以改成byte/long等):
enum 权限 : byte { 读 = 1, 写 = 2, 执行 = 4 } // 显式指定底层类型
System.Enum的作用:
提供了一套方法,让所有枚举都能方便地操作(比如ToString()、CompareTo())。
但这些方法在Enum类里是抽象的,具体实现由CLR魔法完成。
4. 重要限制(直接继承System.Enum不行!)
虽然enum继承自System.Enum,但你不能手动继承它:
// ❌ 编译错误!不能显式继承Enum
enum 我的枚举 : System.Enum { A, B }
// ✅ 正确写法(enum默认就继承Enum)
enum 我的枚举 { A, B }
5. 实用技巧(基于基类的方法)
(1) 获取枚举值的名字
Console.WriteLine(颜色.绿.ToString()); // 输出:"绿"
(2) 位标志([Flags])判断
[Flags]
enum 权限 { 读 = 1, 写 = 2, 执行 = 4 }
var 我的权限 = 权限.读 | 权限.写;
Console.WriteLine(我的权限.HasFlag(权限.写)); // true
(3) 自定义枚举扩展方法
static string 描述(this 颜色 c) => c switch
{
颜色.红 => "热情红色",
颜色.绿 => "自然绿色",
_ => "其他"
};
Console.WriteLine(颜色.绿.描述()); // 输出:"自然绿色"
6. 总结对比表
| 特性 | enum(枚举) |
System.Enum(基类) |
|---|---|---|
| 类型 | 值类型(继承ValueType) |
抽象类(由CLR特殊实现) |
| 可否继承 | ❌ 不能继承其他类 | ❌ 不能手动继承 |
| 主要作用 | 定义一组固定常量 | 提供枚举的通用操作方法 |
总结口诀
🔹 枚举基类是Enum,所有操作它支撑
🔹 不能继承不能改,CLR魔法后台生
🔹 GetValues查列表,Parse转换字符串
🔹 若要扩展加方法,this一挂真方便
就像所有手机充电口都遵循USB标准:
你的枚举 → 具体手机型号
System.Enum→ USB标准规范
CLR → 电网(提供底层电力支持)
使用原则:
不需要主动操作System.Enum,但知道它的存在有助于理解枚举能力来源。
需要高级枚举操作时(如遍历、转换),直接用Enum.方法()。
internal class Program
{
static unsafe void Main(string[] args)
{
var colorType = typeof(Color);
Console.WriteLine($"枚举的基类是{colorType.BaseType}");
Console.WriteLine($"成员类型:{Enum.GetUnderlyingType(colorType)}");
var red = Color.Red;
if(red== Color.Red)
{
Console.WriteLine($"{Color.Red}={(int)Color.Red}");
}
Console.ReadKey();
}
}
枚举的基类是System.Enum
成员类型:System.Byte
Red=0
通俗解释:枚举的比较 —— “选择题答案的对错判断”
1. 核心结论
枚举的比较本质是整数的比较(因为enum底层是int/byte等数值类型)。
但直接用==或Equals()更直观,避免手动转数字比较(容易出错)。
示例:
enum 水果 { 苹果, 香蕉, 橙子 }
水果 我选 = 水果.香蕉;
水果 你选 = 水果.橙子;
// ✅ 推荐:直接用 == 或 Equals()
bool 相同吗 = (我选 == 你选); // false
bool 仍是香蕉 = 我选.Equals(水果.香蕉); // true
// ❌ 避免:手动转数字比较(除非有特殊需求)
bool 用数字比 = ((int)我选 == 1); // true(但代码难懂)
2. 三种比较方式
| 方式 | 代码示例 | 适用场景 |
|---|---|---|
==运算符 |
if (枚举A == 枚举B) |
大多数情况(最直观) |
Equals() |
if (枚举A.Equals(枚举B)) |
需要兼容泛型或object时 |
HasFlag() |
if (权限.HasFlag(权限.写)) |
检查[Flags]枚举是否包含某标志位 |
3. 实际场景示例
场景1:状态机判断
enum 订单状态 { 待支付, 已发货, 已完成 }
订单状态 当前状态 = 订单状态.已发货;
if (当前状态 == 订单状态.已完成)
{
Console.WriteLine("放烟花庆祝!");
}
场景2:[Flags]位标志检查
[Flags]
enum 权限 { 无 = 0, 读 = 1, 写 = 2, 执行 = 4 }
权限 我的权限 = 权限.读 | 权限.写;
// 检查是否有"写"权限
if (我的权限.HasFlag(权限.写))
{
Console.WriteLine("允许修改文件");
}
场景3:安全转换后比较
enum 错误码 { 成功 = 200, 未找到 = 404 }
int 用户输入 = 404;
// 先检查是否合法的枚举值
if (Enum.IsDefined(typeof(错误码), 用户输入))
{
错误码 code = (错误码)用户输入;
if (code == 错误码.未找到) Console.WriteLine("重新请求资源");
}
4. 特殊情况处理
(1) 混合类型比较
水果 我选 = 水果.苹果;
object 你选 = 水果.苹果;
// ❌ 错误:== 对 object 无效
// bool 结果 = (我选 == 你选);
// ✅ 正确:用 Equals()
bool 结果 = 我选.Equals(你选); // true
(2) 默认值比较
水果 默认值 = default; // 等同于 水果.苹果(因为第一个值是0)
if (默认值 == 水果.苹果)
{
Console.WriteLine("默认值总是第一个选项");
}
5. 性能小贴士
==最快:直接比较底层整数。
HasFlag()稍慢:内部需要位运算,但可读性更好。
避免频繁转换:如无必要,不要反复(int)枚举。
总结口诀
🔹 枚举比较像数字,==和Equals随便使
🔹 位标志用HasFlag,代码清晰不惹事
🔹 混合类型比Equals,默认值首项要牢记
🔹 莫转数字硬比较,除非性能特殊需
就像考试判卷:
==→ 直接对比答案选项(A、B、C)
HasFlag→ 多选题(检查是否选了B)
强制转数字 → 把选项换成题号(如B→2),容易眼花!
黄金法则:
优先用==或Equals
[Flags]枚举必用HasFlag
不要手动转int比较(除非处理底层协议或位运算)
static unsafe void Main(string[] args)
{
Color red = Color.Red;
Color green = Color.Green;
Type colorType = typeof(Color);
Console.WriteLine($"CompareTo比较:{red.CompareTo(Color.Red)}");
Console.WriteLine($"CompareTo比较:{red.CompareTo(green)}");
Console.WriteLine($"Equals比较:{red.Equals(green)}");
Console.ReadKey();
}
CompareTo比较:0
CompareTo比较:-1
Equals比较:False
通俗解释:枚举的CompareTo比较 —— “比赛排名怎么比?”
1. 核心结论
CompareTo 是比较两个枚举值的“顺序”(谁大谁小)。
底层逻辑:CompareTo实际比较的是枚举的整数值(默认从0开始)。
📌 一句话总结:
CompareTo告诉你是A在B前(-1)、相等(0)、还是A在B后(1)。
2. 基本用法(示例)
enum 成绩等级 { 不及格, 及格, 良好, 优秀 }
成绩等级 张三 = 成绩等级.良好; // 底层值=2
成绩等级 李四 = 成绩等级.优秀; // 底层值=3
int 比较结果 = 张三.CompareTo(李四); // 2 < 3 → -1(张三比李四“小”)
Console.WriteLine(比较结果); // 输出:-1
可能的返回值:
| 返回值 | 含义 | 举例(成绩等级) |
|---|---|---|
| -1 | 当前值(张三)在目标值(李四)之前 | 良好 < 优秀 |
| 0 | 两值相等 | 及格 == 及格 |
| 1 | 当前值在目标值之后 | 优秀 > 及格 |
3. 实际应用场景
场景1:排序学生成绩
var 学生 = new List<成绩等级> { 成绩等级.优秀, 成绩等级.及格, 成绩等级.良好 };
学生.Sort(); // 自动调用CompareTo
foreach (var 等级 in 学生)
{
Console.WriteLine(等级); // 输出:及格 → 良好 → 优秀
}
场景2:判断游戏难度
enum 难度 { 简单, 普通, 困难 }
难度 当前难度 = 难度.普通;
if (当前难度.CompareTo(难度.简单) > 0)
{
Console.WriteLine("当前不是简单模式"); // ✔ 会执行
}
场景3:[Flags]枚举比较(慎用!)
[Flags]
enum 权限 { 无 = 0, 读 = 1, 写 = 2 }
权限 我的权限 = 权限.读 | 权限.写;
// ❌ 无意义!CompareTo不适合位标志枚举
int 结果 = 我的权限.CompareTo(权限.写);
// 结果可能是任意值(因为 3 vs 2 的位运算无明确顺序)
4. 注意事项
默认顺序依赖声明顺序
enum 方向 { 上, 下, 左, 右 } // 上=0 < 下=1 < 左=2 < 右=3
手动赋值后顺序可能变
enum 优先级 { 低 = 10, 中 = 5, 高 = 1 }
// CompareTo结果:高(1) < 中(5) < 低(10)
不要用于[Flags]枚举
位标志枚举(如权限)的值是组合的(如读|写=3),CompareTo的逻辑会混乱。
5. 替代方案(更直观的写法)
如果只是想检查大小关系,直接用>/<更清晰:
if (当前难度 > 难度.简单) // 等价于 CompareTo > 0
{
Console.WriteLine("挑战升级!");
}
总结口诀
🔹 CompareTo比顺序,返回-1/0/1
🔹 默认按声明排,手动赋值要小心
🔹 位标志别乱用,大于小于更省心
🔹 排序算法内部用,日常代码少较真
就像比赛颁奖台:
-1 → 你站在对手左边(名次更高)
0 → 你和对手并列
1 → 你站在对手右边(名次更低)
使用建议:
需要排序时用CompareTo(如List.Sort())。
其他情况直接用==/>/<。
通俗解释:枚举的遍历 —— “把选择题的选项全看一遍”
1. 核心比喻:菜单点菜
枚举类型 → 餐厅的菜单(固定几道菜:红烧肉、清蒸鱼、炒青菜)。
遍历枚举 → 把菜单上的每道菜挨个看一遍,决定吃什么。
示例:
enum 菜品 { 红烧肉, 清蒸鱼, 炒青菜 }
// 遍历所有菜品
foreach (菜品 菜 in Enum.GetValues(typeof(菜品)))
{
Console.WriteLine($"今日供应:{菜}");
}
输出:
今日供应:红烧肉
今日供应:清蒸鱼
今日供应:炒青菜
2. 两种遍历方式
(1) 遍历值(Enum.GetValues)
foreach (var 值 in Enum.GetValues(typeof(菜品)))
{
Console.WriteLine(值); // 直接输出枚举项
}
适用场景:需要用到枚举值本身(如生成下拉菜单选项)。
(2) 遍历名字(Enum.GetNames)
foreach (var 名字 in Enum.GetNames(typeof(菜品)))
{
Console.WriteLine(名字); // 输出字符串:"红烧肉"、"清蒸鱼"...
}
适用场景:需要处理枚举的文本表示(如保存到配置文件)。
3. 实际应用场景
场景1:动态生成UI选项
// 假设有一个下拉菜单需要填充
下拉菜单.Items.Clear();
foreach (菜品 菜 in Enum.GetValues(typeof(菜品)))
{
下拉菜单.Items.Add(菜.ToString());
}
场景2:校验用户输入
string 用户输入 = "清蒸鱼";
bool 是否合法 = Enum.IsDefined(typeof(菜品), 用户输入); // true
场景3:自动生成文档
StringBuilder 文档 = new StringBuilder();
文档.AppendLine("可选菜品:");
foreach (string 菜名 in Enum.GetNames(typeof(菜品)))
{
文档.AppendLine($"- {菜名}");
}
File.WriteAllText("菜单.txt", 文档.ToString());
4. 进阶技巧
(1) 过滤特殊值(如[Flags]枚举的0值)
[Flags] enum 权限 { 无 = 0, 读 = 1, 写 = 2 }
foreach (权限 p in Enum.GetValues(typeof(权限)))
{
if (p != 权限.无) // 跳过"无"权限
Console.WriteLine(p);
}
(2) 按自定义顺序遍历
// 按枚举值的数字大小排序
var 排序后的值 = Enum.GetValues(typeof(菜品)).Cast<菜品>().OrderBy(v => (int)v);
(3) 多语言支持
Dictionary<菜品, string> 多语言菜单 = new Dictionary<菜品, string>
{
{ 菜品.红烧肉, "Braised Pork" },
{ 菜品.清蒸鱼, "Steamed Fish" }
};
foreach (菜品 菜 in Enum.GetValues(typeof(菜品)))
{
Console.WriteLine(多语言菜单[菜]); // 输出英文菜名
}
5. 常见问题
Q:遍历会包括所有组合值吗?([Flags]枚举)
A:不会。GetValues只返回明确定义的单个值:
[Flags] enum 权限 { 无 = 0, 读 = 1, 写 = 2, 读写 = 3 }
// 遍历结果:无、读、写、读写(不会出现其他组合)
Q:性能如何?
A:GetValues会生成新数组,避免在频繁循环中调用,可缓存结果:
// 缓存提高性能
static readonly 菜品[] 所有菜品 = (菜品[])Enum.GetValues(typeof(菜品));
总结口诀
🔹 遍历枚举两招鲜,GetValues和GetNames
🔹 前者拿值后者名,菜单选项轻松填
🔹 位标志枚举要过滤,跳过0值更安全
🔹 频繁调用需缓存,性能优化记心间
就像查通讯录:
GetValues→ 翻出所有联系人(直接拨号)
GetNames→ 只看联系人名字(复制到记事本)
黄金法则:
需要值 → GetValues
需要字符串 → GetNames
处理[Flags] → 记得检查0值
static unsafe void Main(string[] args)
{
//GetNames() 常数名称组成的数组
//GetValues() 常数值组成的数组
Type colorType = typeof(Color);
Console.WriteLine($"GetName():{Enum.GetName(colorType,3)}");//查找某一个项
string[] names = Enum.GetNames(colorType);//获取所有项
foreach (var item in names)
{
Console.WriteLine(item);
}
Array array = Enum.GetValues(colorType);
foreach (var item in array)
{
Console.WriteLine($"{(byte)item}");
}
Console.ReadKey();
}
通俗解释:枚举的类型转换 —— “翻译官的工作”
1. 核心比喻:语言翻译
枚举 就像一本多国语言词典,存储了 单词(枚举值) 和对应的 编号(整数)。
类型转换 就是让计算机在 文字(枚举名)↔ 数字(整数值)↔ 枚举 之间互相翻译。
2. 三种常见转换场景
(1) 枚举 → 整数(显式转换)
enum 颜色 { 红 = 1, 绿 = 2, 蓝 = 4 }
颜色 我的颜色 = 颜色.绿;
int 颜色编号 = (int)我的颜色; // 显式转为int → 2
💡 用途:存储到数据库、网络传输等需要数字的场景。
(2) 整数 → 枚举(强制转换)
int 用户输入 = 2;
颜色 解析结果 = (颜色)用户输入; // 转为 颜色.绿
// 危险!如果数字不合法:
颜色 非法值 = (颜色)999; // 编译通过,但运行时可能出错
✅ 安全做法:先用
Enum.IsDefined检查合法性。
(3) 字符串 → 枚举(智能解析)
string 用户输入 = "绿";
颜色 解析结果;
bool 成功 = Enum.TryParse(用户输入, out 解析结果); // true → 颜色.绿
// 或者直接解析(失败抛异常)
颜色 强制解析 = Enum.Parse<颜色>("红"); // 颜色.红
💡 用途:处理配置文件、用户输入等文本数据。
3. 实际应用示例
场景1:保存游戏存档
enum 游戏状态 { 未开始, 进行中, 已通关 }
// 存档时转数字
int 存档数据 = (int)当前状态; // 例如:进行中 → 1
// 读档时转回枚举
游戏状态 加载的状态 = (游戏状态)存档数据;
场景2:处理API响应
// API返回:"status": "success"
string api响应 = "success";
Enum.TryParse<状态码>(api响应, out var 状态); // 状态 = 状态码.success
场景3:动态绑定UI控件
// 下拉菜单显示枚举项名
下拉菜单.DataSource = Enum.GetNames(typeof(颜色));
// 用户选择后转为枚举值
颜色 选中颜色 = Enum.Parse<颜色>(下拉菜单.SelectedValue.ToString());
4. 特殊转换技巧
(1) 位标志枚举([Flags])的转换
[Flags] enum 权限 { 无 = 0, 读 = 1, 写 = 2 }
// 数字转组合权限
权限 我的权限 = (权限)3; // 读 + 写
// 字符串解析组合值
权限 解析权限 = Enum.Parse<权限>("读, 写"); // 读 | 写
(2) 忽略大小写解析
颜色 c = Enum.Parse<颜色>("RED", ignoreCase: true); // 颜色.红
(3) 默认值处理
// 如果转换失败,返回默认值
颜色 安全解析 = Enum.TryParse<颜色>("无效值", out var result)
? result
: 颜色.红;
5. 常见坑与避坑指南
| 问题 | 错误示例 | 正确做法 |
|---|---|---|
| 数字超出枚举定义范围 | (颜色)99 |
先用Enum.IsDefined检查 |
| 字符串拼写错误 | Parse("蓝绿") |
用TryParse+错误处理 |
| 位标志枚举未加[Flags] | (权限)3 被当作未知值 |
添加[Flags]特性 |
| 频繁调用Parse性能差 | 循环内直接Parse |
缓存解析结果或预编译表达式 |
总结口诀
🔹 枚举转数显式转,数转枚举要检验
🔹 字符串用TryParse,安全灵活两相全
🔹 位标志需[Flags],组合解析逗号连
🔹 大小写可忽略,性能优化记心间
就像翻译官的工作:
数字 → 枚举 → 把编号翻译成单词
枚举 → 数字 → 把单词编码成数字
字符串 → 枚举 → 听懂用户说的外语
黄金法则:
优先用TryParse而非Parse(防崩溃)
处理用户输入时永远验证范围
[Flags]枚举要用位运算逻辑处理
internal class Program
{
static unsafe void Main(string[] args)
{
Type colorType = typeof(Color);
Color red = (Color)Enum.Parse(colorType, "Red");
bool IsParsed = Enum.TryParse<Color>("Red", out Color result);
if(IsParsed)
{
Console.WriteLine("转换成功");
}
else
{
Console.WriteLine("转换失败");
}
object obj = Enum.ToObject(colorType, 2);
var green = (Color)1;
Console.ReadKey();
}
}
通俗解释:位标志枚举 —— “用开关组合控制状态”
1. 核心比喻:电灯开关面板
普通枚举 → 单个开关(要么开,要么关)。
位标志枚举 → 多个独立开关(可以同时开灯、开风扇、开空调)。
示例:
[Flags] // 关键标记!
enum 家电控制
{
关 = 0, // 0b0000
灯 = 1, // 0b0001
风扇 = 2, // 0b0010
空调 = 4, // 0b0100
全开 = 灯 | 风扇 | 空调 // 0b0111
}
家电控制 当前状态 = 家电控制.灯 | 家电控制.空调; // 同时开灯和空调
2. 为什么用位标志枚举?
| 需求 | 普通枚举 | 位标志枚举 |
|---|---|---|
| 单一状态 | 状态.运行中 |
同样可用 |
| 组合状态 | ❌ 无法表示 | ✅ 灯+风扇+空调 |
| 快速检查 | ❌ 只能等于判断 | ✅ 用HasFlag查单个开关 |
| 节省存储 | ❌ 每个状态单独存 | ✅ 一个数字存所有状态(如int存32个开关) |
3. 四大核心操作
(1) 开启开关(叠加状态)
家电控制 状态 = 家电控制.灯;
状态 |= 家电控制.风扇; // 添加风扇(现在:灯+风扇)
(2) 关闭开关(移除状态)
状态 &= ~家电控制.灯; // 关灯(现在:只剩风扇)
(3) 检查开关(是否开启)
if (状态.HasFlag(家电控制.空调))
{
Console.WriteLine("空调正在运行");
}
(4) 切换开关(反转状态)
状态 ^= 家电控制.风扇; // 如果风扇开则关,关则开
4. 实际应用场景
场景1:文件权限系统
[Flags]
enum 文件权限
{
无 = 0,
读 = 1, // 0b0001
写 = 2, // 0b0010
执行 = 4 // 0b0100
}
// 用户权限设置
文件权限 user1 = 文件权限.读 | 文件权限.写; // 可读可写
// 检查权限
if (user1.HasFlag(文件权限.写))
{
Console.WriteLine("允许保存文件");
}
场景2:游戏技能组合
[Flags]
enum 技能
{
无 = 0,
火球 = 1,
冰冻 = 2,
治疗 = 4,
连击 = 火球 | 冰冻 // 组合技能
}
技能 玩家技能 = 技能.火球 | 技能.治疗;
场景3:硬件设备状态
[Flags]
enum 设备状态
{
正常 = 0,
卡纸 = 1,
缺墨 = 2,
过热 = 4
}
设备状态 打印机状态 = 设备状态.卡纸 | 设备状态.缺墨;
Console.WriteLine(打印机状态); // 输出:"卡纸, 缺墨"
5. 必须遵守的规则
数值必须为2的幂次方(1, 2, 4, 8, 16…)
// ✅ 正确
enum 示例 { 选项1 = 1, 选项2 = 2, 选项3 = 4 }
// ❌ 错误(3不是2的幂次方)
enum 错误示例 { 选项1 = 1, 选项2 = 2, 选项3 = 3 }
必须加[Flags]特性
不加时,ToString()只能显示数字而非组合名称。
建议提供0值
表示”无任何标志”状态(如权限.无)。
6. 如何优雅打印组合值?
[Flags]
enum 宠物 { 无 = 0, 狗 = 1, 猫 = 2, 鸟 = 4 }
宠物 我的宠物 = 宠物.狗 | 宠物.猫;
Console.WriteLine(我的宠物); // 输出:"狗, 猫"(而不是数字3)
总结口诀
🔹 位标志枚举真神奇,多个状态一身集
🔹 数值必是2的幂,[Flags]特性别忘记
🔹 | 叠加 &~ 删, HasFlag 查仔细
🔹 权限系统最常用,开关组合超省力
就像多功能遥控器:
普通枚举 → 只能控制电视开关
位标志枚举 → 能同时控制电视、空调、灯光(且互不干扰)
黄金法则:
需要组合状态时必用位标志枚举
所有选项值必须是1, 2, 4, 8...
操作时用位运算符(|、&、^)
using System;
using System.IO;
using System.Reflection;
namespace HelloWorld
{
//enum = 枚举;struct=结构体;=>值类型
//class=类/类型;interface=接口;delegate=委托;=>引用类型
//枚举是整型 隐式继承于int
internal enum Color : byte
{
Red,
Green,
Yellow,
Orange,
Cyan,
Blue,
Purple,
}
internal enum Week
{
Monday = 1,
Tuesday =4 ,
Wednesday =8,
Thursday,
Friday,
Saturday,
Sunday,
}
public enum SingleRole : byte
{
Insert = 0, //增加
Delete = 1, //删除
Select = 2, //查询
Update = 4, //更新
}
[Flags]
public enum MultiRole : byte
{
Insert = 0, //增加
Delete = 1, //删除
Select = 2, //查询
Update = 4, //更新
}
/* 课程:位标志(枚举)
* 讲师:重庆教主
*/
internal class Program
{
static unsafe void Main(string[] args)
{
Console.WriteLine("枚举的取值范围");
for (int i = 0; i < 8; i++)
Console.WriteLine("{0,3} - {1:G}", i, (SingleRole)i);
Console.WriteLine("位标志的组合值取值范围");
for (int j = 0; j < 8; j++)
Console.WriteLine("{0,3} - {1:G}", j, (MultiRole)j);
string path = Assembly.GetExecutingAssembly().Location;
Console.WriteLine(path);
Console.WriteLine(File.GetAttributes(path));
FileAttributes hidden = FileAttributes.Hidden | FileAttributes.ReadOnly;
File.SetAttributes(path, hidden);
Console.WriteLine(File.GetAttributes(path));
FileAttributes archive = FileAttributes.Archive;
File.SetAttributes(path, archive);
Console.WriteLine(File.GetAttributes(path));
Console.WriteLine($"hidden={(int)hidden}");
Console.WriteLine($"archive={(int)archive}");
Console.ReadKey();
}
}
}
















暂无评论内容