C#中级语法–枚举

    //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:性能如何?

AGetValues会生成新数组,避免在频繁循环中调用,可缓存结果:

// 缓存提高性能
static readonly 菜品[] 所有菜品 = (菜品[])Enum.GetValues(typeof(菜品));

总结口诀

🔹 遍历枚举两招鲜,GetValuesGetNames
🔹 前者拿值后者名,菜单选项轻松填
🔹 位标志枚举要过滤,跳过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();
        }
    }
}

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容