在C#中,枚举(enum)本质上是一种值类型,编译后对应一个继承于System.Enum的整数常量集合。开发者习惯用枚举来表达有限的状态、类型或选项,因为它比裸int更具可读性,也能借助编译器做类型检查。不过真到了要展示、存储或反序列化的时候,往往要把枚举和字符串来回倒腾,这里头的语法细节和坑位值得拆开讲透。

枚举的基础定义与底层存储
定义一个枚举时,如果不显式指定基础类型,默认底层是System.Int32。每个成员其实就是一个命名的常量,在中间语言(IL)层面被标记为字段并附上字面量值。理解这一点很关键,因为后面所有字符串转换,本质上都是在“整数值”“成员名”“附加描述”三者之间建立映射,而不是什么神秘反射操作。
比如下面这段定义,我们给日志级别做了显式赋值。显式赋值能避免新增成员时默认值漂移,也方便和数据库里的tinyint直接对齐。注意枚举成员本身不能包含空格,如果要在界面显示“Information”以外的文字,就得靠特性或字典来补充。
public enum LogLevel : byte
{
Trace = 0,
Debug = 1,
Information = 2,
Warning = 3,
Error = 4,
Critical = 5
}
从内存角度看,一个LogLevel变量只占一个字节,却享有编译期类型安全。当你写LogLevel level = LogLevel.Error;时,赋值器直接把4写进栈上变量。这种轻量特性让枚举非常适合做标志位(配合Flags特性)或作为Dictionary的键。但也正因为它太像整数,很多新手会直接把用户输入的string丢给转换方法,从而引入异常风险。
遍历枚举成员的几种写法
想要列出枚举里所有候选值,最常用的是Enum.GetValues。它返回一个Array,里面按定义顺序装着每个枚举实例。在泛型时代,我们可以用Enum.GetValues<TEnum>()(.NET Core 2.0+)拿到强类型数组,避免后面频繁拆箱。遍历本身不触及任何IO,纯CPU计算,性能可以放心用在下拉框绑定等场景。
下面示例演示如何把上面定义的LogLevel所有成员名和值打印出来。注意GetValues拿到的元素类型是object,需要转回具体枚举才能调用扩展方法。如果枚举带Flags,遍历出的依然是单个位定义,不会自动展开组合值。
foreach (LogLevel level in Enum.GetValues<LogLevel>())
{
Console.WriteLine($"Name={level}, Value={(byte)level}");
}
// 旧版写法,仍广泛存在于存量代码
foreach (var item in Enum.GetValues(typeof(LogLevel)))
{
LogLevel lv = (LogLevel)item;
Console.WriteLine(lv);
}
除了GetValues,还可以用Enum.GetNames只拿字符串数组,或者借助反射拿FieldInfo来读取贴在成员上的特性。如果只是做下拉项,GetValues配合ToString最直观;如果要同时取描述,就得走反射缓存,否则高频调用会有明显开销。实际项目里建议把“枚举+描述”的映射在启动时扫一遍存进静态字典,后面直接查表。
枚举与字符串的相互转换
把枚举变成字符串最简单就是ToString,但它只给成员名。若要反过来,Enum.Parse和Enum.TryParse是标准手段。二者区别在于:前者转换失败直接抛ArgumentException,后者返回bool且不抛异常,更适合处理外部输入。忽略大小写时传true给ignoreCase参数,能省掉自己ToLower的麻烦。
以下代码展示安全转换的典型写法。我们故意用一个不在枚举里的文本去试,TryParse会回退到默认值而不是崩溃。顺带一提,如果字符串其实是数字(如"4"),TryParse也能转成对应的Error,这点比很多人以为的更宽松,但也更容易掩盖脏数据,需要按业务决定是否禁止数字串。
string input = "warning";
if (Enum.TryParse<LogLevel>(input, true, out LogLevel result))
{
Console.WriteLine($"解析成功:{result}");
}
else
{
Console.WriteLine("解析失败,使用默认 Trace");
result = LogLevel.Trace;
}
// 反向:枚举到字符串
string name = LogLevel.Critical.ToString(); // "Critical"
当界面要显示“错误信息”而非“Error”时,单纯ToString就不够了。可以给成员加DescriptionAttribute,再用反射读取。下面示例给出一个通用扩展方法,优先取描述,没有就退回成员名。这样既能保持代码里用英文枚举,又能让终端用户看到本地化文案,还不用每处都写switch。
using System.ComponentModel;
using System.Reflection;
public static class EnumExtensions
{
public static string GetDescription(this Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute attr = fi?.GetCustomAttribute<DescriptionAttribute>();
return attr?.Description ?? value.ToString();
}
}
// 使用
LogLevel lv = LogLevel.Error;
Console.WriteLine(lv.GetDescription());
最后提醒一点,字符串转枚举时不要信任前端传来的任何文本。即便用了TryParse,也应校验结果是否落在合法区间内,尤其是带Flags的枚举,组合值可能超出单成员范围。把转换逻辑收口到一个工具类里,配合单元测试覆盖大小写、空格、越界和数字串,才能让C#枚举在字符串世界里既灵活又稳当。
C#enumstring_conversion修改时间:2026-08-18 19:28:33