C#_一篇文章让你精通枚举(Enum)

什么是枚举?


我上周看到一个新入职的同时写switch语句时, 直接用case 1: ,case 2 ,直接用数字来写代码,后面跟一长排注释,态度很好,但没必要,我告诉他你应该使用枚举。使用枚举就应该像使用excel中使用填充柄功能一样自然,顺畅,因为你不会一个一个输入数字1、2、3·······10000,那样太累,也没有必要。

枚举是一种特殊的类,用于表示一组相关的常量。举个例子,假设我们要表示一周的七天,这七天是固定的,我们可以用枚举来表示,当然你也可以用字符串或者整数表示,但那种表示方式不太好,比如你可以用 1 表示星期一,但是一般人很难将星期一和数字1联系起来,但是我们如果用Monday来表示星期一,懂英语就知道这种表示方式。

为什么要使用枚举?


可读性:代码更容易读懂。例如,DayOfWeek.Monday比1更容易理解。

减少错误:枚举一般会和判断语句结合使用,比如判断今天是否是星期五,我们用if(today == 5), 这种方式可以吗?可以,但是容易写错,但是我们用if(today == Friday),这就不容易写错,所见即所得,没有中间的转换过程,可以减少拼写错误或者使用错误的值。

维护方便:修改或者添加新的枚举值非常方便。假设我们事先约定好一些列数字,比如1代表只读,2代表只写,3代表读写,后来我们又出了一个,用4代表禁止读写,我们只需要在枚举中加一行代码就搞定,很方便。

如何定义枚举?


在C#中,可以使用enum关键字来定义枚举。比如,我们定义一个表示一周七天的枚举:

public enum DayOfWeek
{
    Sunday,//默认从0开始,并按定义文本顺序递增 1
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

在上面的例子中,每个枚举值其实对应一个int整数值,默认从0开始。我们也可以手动指定每个枚举值对应的整数

public enum DayOfWeek
{
    Sunday = 1,
    Monday = 2,
    Tuesday = 3,
    Wednesday = 4,
    Thursday = 5,
    Friday = 6,
    Saturday = 7
}

如何使用枚举?

定义了枚举后,我们可以在代码中使用它。例如:

DayOfWeek today = DayOfWeek.Monday;
Console.WriteLine(today);  // 输出: Monday

// 也可以通过整数转换枚举
int dayNumber = (int)DayOfWeek.Wednesday;
Console.WriteLine(dayNumber);  // 输出: 3

枚举的高级用法

枚举的位标志(Flags)

有时候我们需要一个变量能够表示多个状态。例如,一个文件可以同时具有读、写和执行权限。在这种情况下,可以使用枚举的位标志功能。首先,需要使用[Flags]特性来标识这个枚举,然后定义各个标志位。

//使用这个枚举时,可以进行位运算:
FileAccess access = FileAccess.Read | FileAccess.Write;

Console.WriteLine(access);  // 输出: ReadWrite

// 检查是否包含某个标志
bool canRead = (access & FileAccess.Read) == FileAccess.Read;
Console.WriteLine(canRead);  // 输出: True

bool canExecute = (access & FileAccess.Execute) == FileAccess.Execute;
Console.WriteLine(canExecute);  // 输出: False


[Flags]
public enum FileAccess
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    ReadWrite = Read | Write,
    All = Read | Write | Execute
}

我们来看另外一个枚举位运算的代码

[Flags]
public enum Days
{
    None      = 0b_0000_0000,  // 0
    Monday    = 0b_0000_0001,  // 1
    Tuesday   = 0b_0000_0010,  // 2
    Wednesday = 0b_0000_0100,  // 4
    Thursday  = 0b_0000_1000,  // 8
    Friday    = 0b_0001_0000,  // 16
    Saturday  = 0b_0010_0000,  // 32
    Sunday    = 0b_0100_0000,  // 64
    Weekend   = Saturday | Sunday
}

public class FlagsEnumExample
{
    public static void Main()
    {
        Days meetingDays = Days.Monday | Days.Wednesday | Days.Friday;//星期一三五开会
        Console.WriteLine(meetingDays);
        // Output:
        // Monday, Wednesday, Friday

        Days workingFromHomeDays = Days.Thursday | Days.Friday;
        Console.WriteLine($"Join a meeting by phone on {meetingDays & workingFromHomeDays}");//开会日同时是工作日
        // Output:
        // Join a meeting by phone on Friday

        bool isMeetingOnTuesday = (meetingDays & Days.Tuesday) == Days.Tuesday;
        Console.WriteLine($"会议是否在星期三: {isMeetingOnTuesday}");
        // Output:
        // Is there a meeting on Tuesday: False

        var a = (Days)37;
        Console.WriteLine(a);
        // Output:
        // Monday, Wednesday, Saturday
    }
}

转换枚举和字符串

有时候,我们需要在枚举和字符串之间转换。可以使用Enum.ParseEnum.ToString方法。

// 枚举转字符串
DayOfWeek today = DayOfWeek.Friday;
string todayString = today.ToString();
Console.WriteLine(todayString);  // 输出: Friday

// 字符串转枚举
string dayString = "Sunday";
DayOfWeek day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), dayString);
Console.WriteLine(day);  // 输出: Sunday

遍历枚举

可以使用Enum.GetValues方法来遍历枚举中的所有值。

foreach (DayOfWeek day in Enum.GetValues(typeof(DayOfWeek)))
{
    Console.WriteLine(day);
}

枚举在实际项目中的应用

在实际项目中,枚举可以用于许多场景,以下是一些常见的使用示例:

状态机

在状态机中,枚举可以用来表示各种状态。例如,表示订单的状态:

public enum OrderStatus
{
    Pending,   //悬挂
    Processed, //处理
    Shipped,   //运输
    Delivered, //交付
    Cancelled  //取消
}

在处理订单时,可以根据订单的不同状态执行不同的操作:

public void ProcessOrder(OrderStatus status)
{
    switch (status)
    {
        case OrderStatus.Pending:
            // 处理待处理订单
            break;
        case OrderStatus.Processed:
            // 处理已处理订单
            break;
        case OrderStatus.Shipped:
            // 处理已发货订单
            break;
        case OrderStatus.Delivered:
            // 处理已交付订单
            break;
        case OrderStatus.Cancelled:
            // 处理已取消订单
            break;
        default:
            throw new ArgumentOutOfRangeException(nameof(status), status, null);
    }
}

配置选项

枚举也可以用于表示配置选项。例如,一个日志系统可以有不同的日志级别:

public enum LogLevel
{
    Debug,   //调试
    Info,	 //信息
    Warning, //警告
    Error,   //错误
    Critical //严重错误
}

使用时可以根据不同的日志级别执行不同的操作:

枚举可以很好地表示配置选项。以日志系统为例,不同的日志级别可以用枚举来表示,这样代码更加清晰、可读性更高。以下是一个示例代码,展示了如何在C#中使用枚举表示日志级别:

using System;

namespace LoggingExample
{
    // 定义日志级别的枚举
    public enum LogLevel
    {
        Debug,
        Info,
        Warning,
        Error,
        Critical
    }

    // 日志类
    public class Logger
    {
        private LogLevel _currentLogLevel;

        // 构造函数,初始化日志级别
        public Logger(LogLevel logLevel)
        {
            _currentLogLevel = logLevel;
        }

        // 打印日志的方法
        public void Log(LogLevel level, string message)
        {
            if (level >= _currentLogLevel)
            {
                Console.WriteLine($"[{level}] - {message}");
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 创建一个日志实例,并设置日志级别为Info
            Logger logger = new Logger(LogLevel.Info);

            // 测试日志输出
            logger.Log(LogLevel.Debug, "This is a debug message."); // 这条消息不会被输出
            logger.Log(LogLevel.Info, "This is an info message.");
            logger.Log(LogLevel.Warning, "This is a warning message.");
            logger.Log(LogLevel.Error, "This is an error message.");
            logger.Log(LogLevel.Critical, "This is a critical message.");
        }
    }
}

在这个示例中,我们定义了一个枚举 LogLevel,表示不同的日志级别。然后在 Logger 类中使用这个枚举来控制日志的输出。当日志级别高于或等于当前设置的日志级别时,消息将被输出到控制台。这种方式可以使日志系统的配置选项更加直观和易于维护。你可以根据需要扩展枚举,添加更多的日志级别或其他配置选项。

枚举的常见问题和解决方法


问题1:如何将枚举的描述信息显示给用户?

有时候,我们希望将枚举值的描述信息(而不是枚举值本身)显示给用户。可以使用[Description]特性和反射来实现。

using System;
using System.ComponentModel;
using System.Reflection;

public enum OrderStatus
{
    [Description("待处理")]
    Pending,
    [Description("已处理")]
    Processed,
    [Description("已发货")]
    Shipped,
    [Description("已交付")]
    Delivered,
    [Description("订单取消")]
    Cancelled
}

public static class EnumExtensions //扩张方法
{
    public static string GetDescription(this Enum value)
    {
        FieldInfo field = value.GetType().GetField(value.ToString());//获取字段
        DescriptionAttribute attribute = field.GetCustomAttribute<DescriptionAttribute>();//获取字段上的特性
        return attribute == null ? value.ToString() : attribute.Description;//放回特性的表述信息
    }
}

class Program
{
    static void Main()
    {
        OrderStatus status = OrderStatus.Shipped;
        Console.WriteLine(status.GetDescription());  // 输出: Order shipped
    }
}

问题2:如何确保枚举值唯一且连续?

在定义枚举时,有时需要确保枚举值唯一且连续。可以通过显式赋值来实现。

public enum ErrorCode
{
    None = 0,
    NotFound = 1,
    InvalidParameter = 2,
    Unauthorized = 3,
    InternalError = 4
}

问题3:如何将枚举值与数据库中的值对应?

在将枚举值存储到数据库或从数据库读取时,可以使用枚举的整数值或者字符串值。下面是一个示例,演示如何使用枚举值与数据库中的值进行映射。

public enum UserRole
{
    Admin = 1,
    User = 2,
    Guest = 3
}

// 保存到数据库时,将枚举转换为整数
int roleValue = (int)UserRole.Admin;

// 从数据库读取时,将整数转换为枚举
UserRole role = (UserRole)roleValue;

问题4:如何处理不存在的枚举值?

在某些情况下,可能会遇到不存在的枚举值。可以使用Enum.IsDefined方法来检查枚举值是否存在。

public static bool IsValidEnumValue<T>(int value) where T : Enum
{
    return Enum.IsDefined(typeof(T), value);
}

class Program
{
    static void Main()
    {
        int value = 5;
        bool isValid = IsValidEnumValue<DayOfWeek>(value);
        Console.WriteLine(isValid);  // 输出: False
    }
}

问题5: 使用枚举简化代码

在许多业务逻辑中,使用枚举可以大大简化代码。例如,在处理不同类型的付款方式时,可以使用枚举来表示不同的付款类型。

public enum PaymentMethod
{
    CreditCard,
    DebitCard,
    PayPal,
    BankTransfer
}

public void ProcessPayment(PaymentMethod method)
{
    switch (method)
    {
        case PaymentMethod.CreditCard:
            // 处理信用卡付款
            break;
        case PaymentMethod.DebitCard:
            // 处理借记卡付款
            break;
        case PaymentMethod.PayPal:
            // 处理PayPal付款
            break;
        case PaymentMethod.BankTransfer:
            // 处理银行转账付款
            break;
        default:
            throw new ArgumentOutOfRangeException(nameof(method), method, null);
    }
}

问题6: 枚举如何与自定义属性结合

可以将枚举与自定义属性结合使用,为枚举值附加更多信息。例如,为枚举值附加显示名称或颜色信息。

public class DisplayAttribute : Attribute
{
    public string Name { get; }
    public string Color { get; }

    public DisplayAttribute(string name, string color)
    {
        Name = name;
        Color = color;
    }
}

public enum TrafficLight
{
    [Display("Stop", "Red")]
    Red,
    [Display("Caution", "Yellow")]
    Yellow,
    [Display("Go", "Green")]
    Green
}

public static class EnumExtensions
{
    public static DisplayAttribute GetDisplayAttribute(this Enum value)
    {
        FieldInfo field = value.GetType().GetField(value.ToString());
        return field.GetCustomAttribute<DisplayAttribute>();
    }
}

class Program
{
    static void Main()
    {
        TrafficLight light = TrafficLight.Red;
        DisplayAttribute display = light.GetDisplayAttribute();
        Console.WriteLine($"{display.Name} - {display.Color}");  // 输出: Stop - Red
    }
}

定义:上下文无关文法是一种描述形式语言的数学模型,由四元组 G=(V,Σ,R,S) 构成。其中,V 是非终结符集合,Σ 是终结符集合,R 是产生式规则集合,S 是起始符号。 示例:在文档中,有 G(E) 和 G(S) 等上下文无关文法,用于描述表达式的结构。例如,G(E) 的定义如下: E→T∣E+T∣E−T T→F∣T∗F∣T/F F→(E)∣i 这里,E、T、F 是非终结符,而 +、−、∗、/、(、) 和 i 是终结符。该文法用于描述算术表达式的构造方式。 推导是根据文法规则从起始符号逐步生成句子的过程,分为两种类型: 最左推导:始终扩展当前最左边的未展开非终结符。 最右推导:始终扩展当前最右边的未展开非终结符。 例如,在 G(N) 的上下文无关文法中,数字串的最左推导过程可以表示为: N⇒ND⇒NDD⇒⋯⇒DDD⇒0DDD⇒01DD⇒012D⇒0127 语法树是通过图形方式展示字符串如何根据文法规则进行推导的结构。它清晰地反映了推导过程中的层次关系。例如,对于表达式 i+i∗i,其语法树可以直观地展示操作符和操作数之间的层次结构。 如果一个句子存在多个不同的语法树,即可以通过多种推导过程生成,那么这个文法就被认为是二义性的。例如,句子 iiiei 有两个可能的语法树,这表明该文法存在二义性。 在自动机理论中,确定化是将非确定有限自动机(NFA)转换为确定有限自动机(DFA),以确保每个状态在读取输入符号时只有一个确定的转移路径。最小化则是去除 DFA 中的冗余状态,以获得更简洁的模型。文档中提供了 DFA 确定化和最小化的详细步骤示例。 正则表达式是一种用于匹配字符串模式的工具。文档中给出了许多正则表达式的例子,例如 (0∣1)∗01,用于匹配所有以“01”结尾的由 0 和 1 组成的字符串。正则表达式在文本处理和模式匹配中具有广泛应用。 综上所述,编译原理不仅涉
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白茶等风12138

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值