在C#中将一个字符串转换为枚举类型

1271

在C#中将一个字符串转换为枚举值的最佳方法是什么?

我有一个包含枚举值的HTML选择标签。当页面被提交时,我希望获取这个值(它将以字符串形式存在)并将其转换为相应的枚举值。

在理想的情况下,我可以这样做:

StatusEnum MyStatus = StatusEnum.Parse("Active");

但那不是有效的代码。


6
请尝试这个:Enum.TryParse("Active", out StatusEnum yourStatus);它的作用是将字符串"Active"转换成一个枚举类型StatusEnum的值,并将结果存储在变量yourStatus中。 - گلی
29个回答

2094
在.NET Core和.NET Framework≥4.0中,有一个通用的解析方法:解析方法
Enum.TryParse("Active", out StatusEnum myStatus);

这也包括C#7的新内联out变量,因此它执行try-parse、转换为显式枚举类型并初始化+填充myStatus变量。
如果您可以访问C#7和最新的.NET,则这是最佳方法。
在.NET中,这相当丑陋(直到4或以上版本):
StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);

我倾向于这样简化:

public static T ParseEnum<T>(string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

然后我可以这样做:
StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");

评论中提出的一个建议是添加一个扩展程序,这很简单:

public static T ToEnum<T>(this string value)
{
    return (T) Enum.Parse(typeof(T), value, true);
}

StatusEnum MyStatus = "Active".ToEnum<StatusEnum>();

最后,如果无法解析字符串,您可能希望有一个默认的枚举可供使用:
public static T ToEnum<T>(this string value, T defaultValue) 
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    T result;
    return Enum.TryParse<T>(value, true, out result) ? result : defaultValue;
}

这就是呼叫的原因:
StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);

然而,我建议小心将这样的扩展方法添加到string中,因为(没有命名空间控制)它会出现在所有string实例上,无论它们是否持有枚举(因此1234.ToString().ToEnum(StatusEnum.None)是有效但无意义的)。除非您的整个开发团队非常了解这些扩展的功能,否则最好避免在非常特定的上下文中向Microsoft的核心类添加额外的方法。


25
如果性能很重要(通常都是),请查看Mckenzieg1在以下链接中提供的答案:https://dev59.com/3nVD5IYBdhLWcg3wU5-H#38711 - Nash
35
@avinashr在@McKenzieG1的回答中是正确的,但并不总是重要的。例如,如果您为每个解析调用都进行数据库调用,则担心枚举解析将是一个无意义的微观优化。 - Keith
7
@H.M. 我认为这里不适合使用扩展 - 这是一个特殊情况,而且一个扩展将应用于_每个_字符串。不过,如果你真的想这样做,那么这将是一个微不足道的改变。 - Keith
10
Enum.TryParse怎么样? - Elaine
21
很好。你在最后一个例子中需要加上 where T : struct。 - bbrik
显示剩余28条评论

402

54
增加一个布尔型的中间参数来控制大小写敏感性是迄今最安全、最优雅的解决方案。 - DanM7
23
来吧,有多少人实施了从2008年选出的答案,只是向下滚动并发现这是更好(现代化)的答案。 - TEK
我不明白。Parse 会抛出解释性异常,说明转换出现了什么问题(值为 null、空或没有对应的枚举常量),这比 TryParse 的布尔返回值要好得多(后者会压制具体错误)。 - yair
6
当解析整数字符串时,Enum.TryParse<T>(String, T) 存在缺陷。例如,这段代码将会成功地将一个无意义的字符串解析为一个无意义的枚举值:var result = Enum.TryParse("55", out var parsedEnum); - Mass Dot Net
4
在这种情况下,添加 && Enum.IsDefined(typeof(System.DayOfWeek), parsedEnum) 来确保解析的枚举实际存在。 - Walter
显示剩余2条评论

240
注意,Enum.Parse() 的性能并不理想,因为它是通过反射实现的(同样适用于相反的Enum.ToString())。

如果你需要在对性能敏感的代码中将字符串转换为枚举,最好在启动时创建一个Dictionary<String,YourEnum>,并使用它来进行转换。


16
我测量了在桌面电脑上第一次运行时将字符串转换为枚举类型需要3毫秒的时间。(只是为了说明其糟糕程度) - Matthieu Charbonnier
29
哇,3毫秒糟糕到了数量级的地步。 - John Stock
3
你能否在这周围添加一个代码示例,以便我们了解如何替换和使用? - Transformer
3
Enum.Parse使用纳秒级而非毫秒级,在现代.NET版本中至少如此。 我使用BenchmarkDotNet在.NET 6中测试了这段代码: MyEnum myEnum = Enum.Parse<MyEnum>("Seven"); 它产生了以下结果: 平均值:33.19 ns 错误:0.469 ns 标准差:0.392 ns - Fanblade
3
.NET Framework 几乎和 .NET 6 一样快,对于所有 .NET Framework 版本 4.6.1-4.8.1 运行以下代码只需 175 nsEnum.TryParse<MyEnum>(Input, out MyEnum result);。这并不是“糟糕的”。 - Fanblade
显示剩余3条评论

132

你正在寻找Enum.Parse

SomeEnum enum = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue");

4
使用值而不是枚举;SomeEnum value = (SomeEnum)Enum.Parse(typeof(SomeEnum), "EnumValue"); - Allen Wahlberg

43

现在你可以使用扩展方法

public static T ToEnum<T>(this string value, bool ignoreCase = true)
{
    return (T) Enum.Parse(typeof (T), value, ignoreCase);
}

你可以通过以下代码调用它们(这里,FilterType 是一个枚举类型):

FilterType filterType = type.ToEnum<FilterType>();

1
我已经更新了这个方法,将值作为对象传递,并在此方法内将其转换为字符串。这样,我可以使用int值.ToEnum而不仅仅是字符串。 - RealSollyM
2
@SollyM 我认为这是一个可怕的想法,因为这个扩展方法将适用于所有对象类型。在我看来,编写两个扩展方法,一个用于字符串,另一个用于整数,会更加清晰和安全。 - Svish
@Svish,没错。我这样做的唯一原因是我们的代码仅在内部使用,我想避免编写两个扩展程序。而且,由于我们只在字符串或整数转换为枚举时进行转换,否则我认为这不会成为问题。 - RealSollyM
3
无论是内部的还是外部的,我仍然是维护和使用我的代码的人:P 如果每次都要在智能感应菜单中输入ToEnum,我会感到很烦恼。而且正如你所说,由于将枚举转换为字符串或整数的唯一时间是从字符串或整数,因此您可以确信您只需要这两种方法。而两种方法并不比一个方法多得多,特别是当它们如此简短且具有实用性时 :P - Svish

33

注意:

enum Example
{
    One = 1,
    Two = 2,
    Three = 3
}
Enum.(Try)Parse()接受多个逗号分隔的参数,并使用二进制“或”|组合它们。你无法禁用它,而且在我看来你几乎永远不想这样做。
var x = Enum.Parse("One,Two"); // x is now Three

即使Three未定义,x仍将获得int值3。这甚至更糟糕:Enum.Parse()可以给您一个甚至未为枚举定义的值!
我不想经历用户自愿或不情愿地触发此行为的后果。
另外,正如其他人所提到的,对于大型枚举,性能不太理想,即可能值的数量呈线性增长。
我建议如下:
    public static bool TryParse<T>(string value, out T result)
        where T : struct
    {
        var cacheKey = "Enum_" + typeof(T).FullName;

        // [Use MemoryCache to retrieve or create&store a dictionary for this enum, permanently or temporarily.
        // [Implementation off-topic.]
        var enumDictionary = CacheHelper.GetCacheItem(cacheKey, CreateEnumDictionary<T>, EnumCacheExpiration);

        return enumDictionary.TryGetValue(value.Trim(), out result);
    }

    private static Dictionary<string, T> CreateEnumDictionary<T>()
    {
        return Enum.GetValues(typeof(T))
            .Cast<T>()
            .ToDictionary(value => value.ToString(), value => value, StringComparer.OrdinalIgnoreCase);
    }

6
实际上,了解Enum.(Try)Parse接受多个逗号分隔参数,并使用二进制“或”组合它们是非常有用的。这意味着您可以将枚举值设置为2的幂,并且您有一种非常简单的方式来解析多个布尔标志,例如“UseSSL,NoRetries,Sync”。实际上,这可能就是其设计目的。 - pcdev
@pcdev 不确定您是否知道,但此功能旨在帮助支持枚举的标志属性。 (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/enum#enumeration-types-as-bit-flags) - Trisped
这个应该是被接受的答案。主要陷阱。 - wenn32

29

在某个时候,解析的通用版本被添加了。对我而言,这更可取,因为我不需要“尝试”解析,同时我也希望结果是内联的,而不是生成一个输出变量。

ColorEnum color = Enum.Parse<ColorEnum>("blue");

MS 文档:Parse


8
注意:泛型版本并不是 .NET Framework 的一部分。等效的语法是 ColorEnum color = (ColorEnum)Enum.Parse(typeof(ColorEnum), "blue"); - WSC

24
object Enum.Parse(System.Type enumType, string value, bool ignoreCase);

所以如果你有一个名为mood的枚举,它会长成这样:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

   // ...
   Mood m = (Mood) Enum.Parse(typeof(Mood), "Happy", true);
   Console.WriteLine("My mood is: {0}", m.ToString());

19

15

您可以通过设置默认值来扩展接受的答案以避免异常:

public static T ParseEnum<T>(string value, T defaultValue) where T : struct
{
    try
    {
        T enumValue;
        if (!Enum.TryParse(value, true, out enumValue))
        {
            return defaultValue;
        }
        return enumValue;
    }
    catch (Exception)
    {
        return defaultValue;
    }
}

然后你可以这样调用:

StatusEnum MyStatus = EnumUtil.ParseEnum("Active", StatusEnum.None);

如果默认值不是枚举,则 Enum.TryParse 将失败并抛出异常,该异常被捕获。

多年来,在我们的代码中广泛使用此函数的情况下,也许添加这个操作会带来性能成本的信息是有益的!


我不喜欢默认值。它可能导致不可预测的结果。 - Daniël Tulp
7
这会导致异常吗? - andleer
如果枚举值与默认值不属于同一枚举类型,请使用@andleer。 - Nelly
@Nelly 这里是旧代码,但 defaultValue 和方法返回类型都是 T 类型。如果类型不同,则会收到编译时错误:“无法从 'ConsoleApp1.Size' 转换为 'ConsoleApp1.Color'”或其他类型。 - andleer
@andleer,非常抱歉我上次回答你的答案是不正确的。如果有人使用默认值调用此函数且该默认值不是枚举类型,则可能会引发Syste.ArgumentException异常。在C# 7.0中,我无法使用T:Enum的where子句。这就是为什么我使用try catch捕获了这种可能性的原因。 - Nelly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接