在C#中将字符串转换为枚举


894

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

我有一个HTML选择标签,其中包含枚举的值。发布页面后,我要提取值(将以字符串形式)并将其转换为枚举值。

在理想的世界中,我可以这样做:

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

但这不是有效的代码。

Answers:


1507

在.NET Core和.NET> 4中,有一个通用的解析方法

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);

但是,我会小心地向stringas 添加这样的扩展方法(没有名称空间控制),因为它将出现在string它们是否拥有枚举的所有实例上(因此1234.ToString().ToEnum(StatusEnum.None)是有效的,但毫无意义)。通常最好避免使用仅适用于非常特定的上下文的额外方法来使Microsoft的核心类混乱,除非整个开发团队对这些扩展的工作有很好的了解。


17
如果性能至关重要(始终如此),则由下面的Mckenzieg1给出chk答案:stackoverflow.com/questions/16100/…–
Nash

28
@avinashr对@ McKenzieG1的回答是正确的,但这并不总是很重要。例如,如果要为每个解析进行数据库调用,则担心枚举解析将是毫无意义的微优化。
基思

4
@HM我认为扩展名在这里不合适-这是一种特殊情况,扩展名将适用于每个字符串。如果您真的想这样做,那将是一个微不足道的改变。
基思·2013年

7
Enum.TryParse怎么样?
Elaine 2014年

15
非常好。在上一个示例中,您需要一个where T:结构。
bbrik

330

使用Enum.TryParse<T>(String, T)(≥.NET 4.0):

StatusEnum myStatus;
Enum.TryParse("Active", out myStatus);

可以使用C#7.0的参数类型内联将其进一步简化:

Enum.TryParse("Active", out StatusEnum myStatus);

45
添加中间的布尔参数以区分大小写,这是迄今为止最安全,最优雅的解决方案。
DanM7 2014年

18
快点,你们中有多少人实施了2008年选择的答案,只是向下滚动并发现这是更好的(现代)答案。
TEK

@TEK我实际上更喜欢2008年的答案。
Zero3 '16

我不明白 Parse对于转换出错(值was为null,为空或没有对应的枚举常量)引发解释性异常,它比TryParseboolean返回值(抑制具体错误)
好得多

2
解析整数字符串时,Enum.TryParse <T>(String,T)有缺陷。例如,以下代码将成功地将无意义的字符串解析为无意义的枚举: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

请注意Enum.Parse(),由于它是通过反射实现的,因此效果很糟。(的情况相同Enum.ToString,反之亦然。)

如果您需要使用对性能敏感的代码将字符串转换为Enums,最好的选择是Dictionary<String,YourEnum>在启动时创建一个,然后使用它来进行转换。


10
我测量了3毫秒,以便在台式机上的第一次运行中将字符串转换为Enum。(只是为了说明不满的程度)。
Matthieu Charbonnier

12
哇3毫秒是可怕的数量级
约翰·

1
您能否为此添加代码示例,所以我们对如何替换和使用有了一个想法
变压器

如果您的应用程序被100万人使用=>它将使您长达50个小时的生命消耗:)一次页面使用。:P
克特林Rădoi


31

您现在可以使用扩展方法

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 2014年

2
@SollyM我想说这是一个可怕的想法,因为这种扩展方法将适用于所有对象类型。在我看来,两种扩展方法(一种用于字符串,一种用于int)会更干净,更安全。
Svish 2014年

@Svish,是的。我这样做的唯一原因是因为我们的代码仅在内部使用,并且我想避免编写2个扩展。而且由于我们唯一一次转换为Enum的方法是使用string或int,所以我不认为这不是问题。
RealSollyM 2014年

3
@SollyM内部还是没有,我仍然是维护和使用我的代码的人:如果我在每个智能感知菜单中都设置了ToEnum,PI将会很烦恼,就像你说的那样,因为转换到枚举的唯一时间是从字符串或int,您可以确定只需要这两种方法。还有两种方法不止一种,特别是当它们很小且实用类型为:P时
Svish 2014年

20
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());

18

谨防:

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);
    }

4
实际上,知道这一点非常有用Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'。意味着您可以将枚举值设置为2的幂,并且您有一种非常简单的方法来解析多个布尔标志,例如。“ UseSSL,NoRetries,Sync”。实际上,这可能就是它的设计目的。
pcdev


13

您可以使用默认值扩展可接受的答案,以避免出现异常:

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将失败并引发捕获的异常。

在许多地方在我们的代码中使用此函数多年后,也许最好添加此操作会降低性能的信息!


我不喜欢默认值。这可能会导致不可预测的结果。
丹尼尔TULP

5
什么时候会抛出异常?
andleer '16

@andleer(如果枚举值不适合与默认值相同的枚举类型)
Nelly

@Nelly这里的旧代码,但是defaultValue和方法的返回类型均为type T。如果类型不同,则将收到一个编译时错误:“无法从'ConsoleApp1.Size'转换为'ConsoleApp1.Color'”或任何类型。
andleer

@andleer,很抱歉我对您的最后一个答案不正确。如果有人使用非enum类型的默认值调用此函数,则此方法可能会引发Syste.ArgumentException。使用c#7.0,我无法创建T的where子句:Enum。这就是为什么我通过尝试抓住了这种可能性的原因。
Nelly

8

我们无法假设输入完全正确,因此采用了@Keith答案的这种变化形式:

public static TEnum ParseEnum<TEnum>(string value) where TEnum : struct
{
    TEnum tmp; 
    if (!Enum.TryParse<TEnum>(value, true, out tmp))
    {
        tmp = new TEnum();
    }
    return tmp;
}

7
// str.ToEnum<EnumType>()
T static ToEnum<T>(this string str) 
{ 
    return (T) Enum.Parse(typeof(T), str);
}

5

从.NET 4.5中将字符串解析为TEnum而不使用try / catch且不使用TryParse()方法

/// <summary>
/// Parses string to TEnum without try/catch and .NET 4.5 TryParse()
/// </summary>
public static bool TryParseToEnum<TEnum>(string probablyEnumAsString_, out TEnum enumValue_) where TEnum : struct
{
    enumValue_ = (TEnum)Enum.GetValues(typeof(TEnum)).GetValue(0);
    if(!Enum.IsDefined(typeof(TEnum), probablyEnumAsString_))
        return false;

    enumValue_ = (TEnum) Enum.Parse(typeof(TEnum), probablyEnumAsString_);
    return true;
}

1
如果代码已经包含描述,是否需要进行描述?好吧,我做到了:)
jite.gs 2013年

3

使用TryParse的超级简单代码:

var value = "Active";

StatusEnum status;
if (!Enum.TryParse<StatusEnum>(value, out status))
    status = StatusEnum.Unknown;

2

我喜欢扩展方法解决方案。

namespace System
{
    public static class StringExtensions
    {

        public static bool TryParseAsEnum<T>(this string value, out T output) where T : struct
        {
            T result;

            var isEnum = Enum.TryParse(value, out result);

            output = isEnum ? result : default(T);

            return isEnum;
        }
    }
}

下面是我用测试的实现。

using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert;
using static System.Console;

private enum Countries
    {
        NorthAmerica,
        Europe,
        Rusia,
        Brasil,
        China,
        Asia,
        Australia
    }

   [TestMethod]
        public void StringExtensions_On_TryParseAsEnum()
        {
            var countryName = "Rusia";

            Countries country;
            var isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsTrue(isCountry);
            AreEqual(Countries.Rusia, country);

            countryName = "Don't exist";

            isCountry = countryName.TryParseAsEnum(out country);

            WriteLine(country);

            IsFalse(isCountry);
            AreEqual(Countries.NorthAmerica, country); // the 1rst one in the enumeration
        }

1
public static T ParseEnum<T>(string value)            //function declaration  
{
    return (T) Enum.Parse(typeof(T), value);
}

Importance imp = EnumUtil.ParseEnum<Importance>("Active");   //function call

====================完整的程序=====================

using System;

class Program
{
    enum PetType
    {
    None,
    Cat = 1,
    Dog = 2
    }

    static void Main()
    {

    // Possible user input:
    string value = "Dog";

    // Try to convert the string to an enum:
    PetType pet = (PetType)Enum.Parse(typeof(PetType), value);

    // See if the conversion succeeded:
    if (pet == PetType.Dog)
    {
        Console.WriteLine("Equals dog.");
    }
    }
}
-------------
Output

Equals dog.


1

为了提高性能,这可能会有所帮助:

    private static Dictionary<Type, Dictionary<string, object>> dicEnum = new Dictionary<Type, Dictionary<string, object>>();
    public static T ToEnum<T>(this string value, T defaultValue)
    {
        var t = typeof(T);
        Dictionary<string, object> dic;
        if (!dicEnum.ContainsKey(t))
        {
            dic = new Dictionary<string, object>();
            dicEnum.Add(t, dic);
            foreach (var en in Enum.GetValues(t))
                dic.Add(en.ToString(), en);
        }
        else
            dic = dicEnum[t];
        if (!dic.ContainsKey(value))
            return defaultValue;
        else
            return (T)dic[value];
    }

1

我发现这里没有考虑具有EnumMember值的枚举值的情况。所以我们开始:

using System.Runtime.Serialization;

public static TEnum ToEnum<TEnum>(this string value, TEnum defaultValue) where TEnum : struct
{
    if (string.IsNullOrEmpty(value))
    {
        return defaultValue;
    }

    TEnum result;
    var enumType = typeof(TEnum);
    foreach (var enumName in Enum.GetNames(enumType))
    {
        var fieldInfo = enumType.GetField(enumName);
        var enumMemberAttribute = ((EnumMemberAttribute[]) fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), true)).FirstOrDefault();
        if (enumMemberAttribute?.Value == value)
        {
            return Enum.TryParse(enumName, true, out result) ? result : defaultValue;
        }
    }

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

该枚举的示例:

public enum OracleInstanceStatus
{
    Unknown = -1,
    Started = 1,
    Mounted = 2,
    Open = 3,
    [EnumMember(Value = "OPEN MIGRATE")]
    OpenMigrate = 4
}

1

您必须使用Enum.Parse从Enum中获取对象值,之后,您必须将对象值更改为特定的枚举值。可以使用Convert.ChangeType强制转换为枚举值。请看下面的代码片段

public T ConvertStringValueToEnum<T>(string valueToParse){
    return Convert.ChangeType(Enum.Parse(typeof(T), valueToParse, true), typeof(T));
}

1

试试这个例子:

 public static T GetEnum<T>(string model)
    {
        var newModel = GetStringForEnum(model);

        if (!Enum.IsDefined(typeof(T), newModel))
        {
            return (T)Enum.Parse(typeof(T), "None", true);
        }

        return (T)Enum.Parse(typeof(T), newModel.Result, true);
    }

    private static Task<string> GetStringForEnum(string model)
    {
        return Task.Run(() =>
        {
            Regex rgx = new Regex("[^a-zA-Z0-9 -]");
            var nonAlphanumericData = rgx.Matches(model);
            if (nonAlphanumericData.Count < 1)
            {
                return model;
            }
            foreach (var item in nonAlphanumericData)
            {
                model = model.Replace((string)item, "");
            }
            return model;
        });
    }

在此示例中,您可以发送每个字符串,然后设置Enum。如果您Enum有想要的数据,则将其作为Enum类型返回。


1
您正在覆盖newModel每行,因此,如果其中包含破折号,则不会替换。另外,您不必检查字符串是否包含任何内容,可以随时调用Replacevar newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen

@LarsKristensen是的,我们可以创建一个删除非字母数字字符的方法。
AmirReza-Farahlagha

1

不确定何时添加,但是在Enum类上现在有一个

Parse<TEnum>(stringValue)

像这样与示例一起使用:

var MyStatus = Enum.Parse<StatusEnum >("Active")

或通过以下方式忽略大小写:

var MyStatus = Enum.Parse<StatusEnum >("active", true)

这是此方法使用的反编译方法:

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value) where TEnum : struct
    {
      return Enum.Parse<TEnum>(value, false);
    }

    [NullableContext(0)]
    public static TEnum Parse<TEnum>([Nullable(1)] string value, bool ignoreCase) where TEnum : struct
    {
      TEnum result;
      Enum.TryParse<TEnum>(value, ignoreCase, true, out result);
      return result;
    }

0
        <Extension()>
    Public Function ToEnum(Of TEnum)(ByVal value As String, ByVal defaultValue As TEnum) As TEnum
        If String.IsNullOrEmpty(value) Then
            Return defaultValue
        End If

        Return [Enum].Parse(GetType(TEnum), value, True)
    End Function

0
public TEnum ToEnum<TEnum>(this string value, TEnum defaultValue){
if (string.IsNullOrEmpty(value))
    return defaultValue;

return Enum.Parse(typeof(TEnum), value, true);}

0

如果属性名称与您要调用的名称不同(即语言差异),则可以执行以下操作:

MyType.cs

using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

[JsonConverter(typeof(StringEnumConverter))]
public enum MyType
{
    [EnumMember(Value = "person")]
    Person,
    [EnumMember(Value = "annan_deltagare")]
    OtherPerson,
    [EnumMember(Value = "regel")]
    Rule,
}

EnumExtensions.cs

using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public static class EnumExtensions
{
    public static TEnum ToEnum<TEnum>(this string value) where TEnum : Enum
    {
        var jsonString = $"'{value.ToLower()}'";
        return JsonConvert.DeserializeObject<TEnum>(jsonString, new StringEnumConverter());
    }

    public static bool EqualsTo<TEnum>(this string strA, TEnum enumB) where TEnum : Enum
    {
        TEnum enumA;
        try
        {
            enumA = strA.ToEnum<TEnum>();
        }
        catch
        {
            return false;
        }
        return enumA.Equals(enumB);
    }
}

Program.cs

public class Program
{
    static public void Main(String[] args) 
    { 
        var myString = "annan_deltagare";
        var myType = myString.ToEnum<MyType>();
        var isEqual = myString.EqualsTo(MyType.OtherPerson);
        //Output: true
    }     
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.