基础
我们先来看官方文档对于枚举类型的定义:An enumeration type (or enum type) is a value type defined by a set of named constants of the underlying integral numeri type.
翻译过来就是,枚举类型是一个值类型,定义了一系列的命名常量,这些常量都是整数数值类型的。
一个最简单的枚举可以是这样的:
public enum Season
{
Spring,
Summer,
Autumn,
Winter
}
最基础的用法,就是每个枚举值对应着一个整数数值。
但是,如果我们将这个整数视为二进制,或者增加字符串的映射,那枚举还会有更多用途。这篇文章就围绕枚举实现「位标志枚举」、「字符串枚举」展开介绍。
位标志枚举
场景设想
假设现在在一间教室里,老师需要对学生 A, B, C ... G 点到。有没有一种可能,我们仅仅通过一个枚举值,就可以确定当前有哪些学生在场?
二进制思想
这里需要用「**二进制」**的思想。假设每个学生独立占有二进制数值中的「一位」,通过这个状态位的值来确定学生是否在场(0 - 不在场,1 - 在场)。
代码演示
转化成代码,用枚举来表示每一个学生,并对每一位学生赋予一个不同的位标志。使用 currentStatus 来记录当前学生在场状态。(注意: 位标志枚举,需要有 [Flags] 标识。)
using System;
public class Program
{
[Flags]
enum Student
{
A = 0b_1,
B = 0b_10,
C = 0b_100,
D = 0b_1000,
E = 0b_10000,
F = 0b_100000,
G = 0b_1000000,
}
public static void Main()
{
Student currentStatus = 0;
}
}
应用1:“点名”,更新在场状态
假设学生 A 在场,那么有 currentStatus |= Student.A
,此时 currentStatus 的二进制值为 1.
假设学生 G 也在场,那么有 currentStatus |= Student.G
,此时 currentStatus 的二进制值为 1000001.
如果学生 B 原先在场,目前又不在场,如何更新 B 的状态呢?首先对 B 取反码 `Convert.ToString((int)~Student.B, 2)`
,得到 `11111111111111111111111111111101`
. 将该值与当前状态进行与运算:currentStatus &= ~Student.B
,便得到 B 不在场的状态更新。因为 B 专属的状态位为 0,进行与操作,将当前状态对应的状态位置为 0;而其他位置为 1,进行与操作,不会改变当前状态其他位置的值。
小结:「更新在场状态」的处理,可以归纳为两种情况:增加某个枚举状态、移除某个枚举状态。我们可以使用 UpdateStatus 这样一个函数来表示:
void UpdateStatus(Student student, bool additive)
{
if(additive) {
// 添加
currentStatus |= student;
}
else
{
// 移除
currentStatus &= ~student;
}
}
应用2:确定整体在场状态
反过来,假设我们知道 currentStatus 的值为 1000001,是否可以唯一确定一种学生在场状态?答案是肯定的。这里的关键是,每一位的值不会互相干扰。
Console.WriteLine(currentStatus);
// A, G
应用3:确定单个学生在场状态
如果我们想确定学生 A 在场,应该怎么做呢?
if((currentStatus & Student.A) == Student.A) {
Console.WriteLine("A 在场");
}
反之,想确定学生不在场,就是
if((currentStatus & Student.A) != Student.A) {
Console.WriteLine("A 不在场");
}
完整代码
(可以复制到 dotnetfiddle.net/ 点击运行哦~)
using System;
using System.Text;
public class Program
{
[Flags]
public enum Student
{
A = 0b_1,
B = 0b_10,
C = 0b_100,
D = 0b_1000,
E = 0b_10000,
F = 0b_100000,
G = 0b_1000000,
}
static public Student currentStatus = 0;
public static void Main()
{
// 点名
// A 在场
currentStatus |= Student.A;
// G 在场
currentStatus |= Student.G;
// B 在场
currentStatus |= Student.B;
Console.WriteLine(currentStatus);
// B 又不在场了
currentStatus &= ~Student.B;
Console.WriteLine(currentStatus);
UpdateStatus(Student.E, true);
Console.WriteLine(currentStatus);
// 检查整体在场状态
Console.WriteLine(currentStatus);
// 检查单个学生在场状态
if((currentStatus & Student.A) == Student.A) {
Console.WriteLine("A 在场");
}
if((currentStatus & Student.A) != Student.A) {
Console.WriteLine("A 不在场");
}
// 打印 B 的反码
Console.WriteLine(Convert.ToString((int)~Student.B, 2));
}
static public void UpdateStatus(Student student, bool additive)
{
if(additive) {
// 添加
currentStatus |= student;
}
else
{
// 移除
currentStatus &= ~student;
}
}
}
字符串枚举
C# 枚举只支持整数数值类型,如果要枚举字符串类型,该怎么处理呢?
注意到官方文档上有提到:不能在枚举类型的定义内定义方法。 若要向枚举类型添加功能,请创建扩展方法。
我们考虑使用「扩展方法」来实现字符串枚举。
这里以常见的「错误码映射成提示语」的场景来说明:
public enum ErrorCode {
Default,
Error,
}
public static class ErrorCodeExtension
{
private static readonly Dictionary<ErrorCode, string> codeDictionary = new()
{
{ ErrorCode.Default, "This is Default." },
{ ErrorCode.Error, "Warning! This is Error." },
};
public static string GetString(this ErrorCode code)
{
codeDictionary.TryGetValue(code, out var desc);
return desc;
}
}
完整代码:(可以复制到 dotnetfiddle.net/ 点击运行哦~)
using System;
using System.Text;
using System.Collections.Generic;
public enum ErrorCode {
Default,
Error,
}
public static class ErrorCodeExtension
{
private static readonly Dictionary<ErrorCode, string> codeDictionary = new()
{
{ ErrorCode.Default, "This is Default." },
{ ErrorCode.Error, "Warning! This is Error." },
};
public static string GetString(this ErrorCode code)
{
codeDictionary.TryGetValue(code, out var desc);
return desc;
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(ErrorCode.Error.GetString());
Console.WriteLine(ErrorCode.Default.GetString());
}
}