C# 枚举高阶用法:位标志枚举与字符串枚举

3,207 阅读4分钟

基础

我们先来看官方文档对于枚举类型的定义: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());
                
        }
}