什么是枚举?
我上周看到一个新入职的同时写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.Parse
和Enum.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
}
}
总结
我们用人话总结一下: 在你编写源代码的时候, 千万不要直接在源文件中使用数字/字符/字符串 , 应该改为使用枚举, 这样做的好处是, 将来你修改代码时,只要在枚举中修改就行了, 而不用去整个代码中找哪里1 改成 2, 哪里2 又改成3,那样太费力了, 还有一个更大的好处是, 使用枚举可以让代码所见即所得, 其他人不需要问1代表什么,2 代表什么意思,这样在团队协作时,省的别人啥事都来问你。