Converter uma sequência de caracteres em uma enumeração em C #


894

Qual é a melhor maneira de converter uma seqüência de caracteres em um valor de enumeração em C #?

Eu tenho uma marca de seleção HTML que contém os valores de uma enumeração. Quando a página é postada, quero pegar o valor (que estará no formato de uma sequência) e convertê-lo no valor de enumeração.

Em um mundo ideal, eu poderia fazer algo assim:

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

mas esse não é um código válido.

Respostas:


1510

No .NET Core e .NET> 4, existe um método de análise genérico :

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

Isso também inclui as novas outvariáveis embutidas do C # 7 , o que faz a tentativa de análise, a conversão para o tipo de enum explícito e inicializa + preenche a myStatusvariável.

Se você tiver acesso ao C # 7 e ao .NET mais recente, é a melhor maneira.

Resposta original

No .NET, é bastante feio (até 4 ou mais):

StatusEnum MyStatus = (StatusEnum) Enum.Parse(typeof(StatusEnum), "Active", true);

Costumo simplificar isso com:

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

Então eu posso fazer:

StatusEnum MyStatus = EnumUtil.ParseEnum<StatusEnum>("Active");

Uma opção sugerida nos comentários é adicionar uma extensão, que é bastante simples:

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

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

Por fim, convém usar uma enumeração padrão se a sequência não puder ser analisada:

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

O que faz desta a chamada:

StatusEnum MyStatus = "Active".ToEnum(StatusEnum.None);

No entanto, eu seria cuidadoso ao adicionar um método de extensão como este para stringque (sem controle de namespace) apareça em todas as instâncias, stringse eles possuem um enum ou não (isso 1234.ToString().ToEnum(StatusEnum.None)seria válido, mas sem sentido). Geralmente, é melhor evitar bagunçar as classes principais da Microsoft com métodos extras que só se aplicam em contextos muito específicos, a menos que toda a equipe de desenvolvimento tenha um entendimento muito bom do que essas extensões fazem.


17
Se a performance for importante (o que sempre é), responda chk dada por Mckenzieg1 abaixo: stackoverflow.com/questions/16100/…
Nash

28
@avinashr está certo sobre a resposta de @ McKenzieG1, mas nem sempre é importante. Por exemplo, seria uma micro otimização inútil se preocupar com a análise de enum se você estivesse fazendo uma chamada de banco de dados para cada análise.
1919 Keith

4
@HM Eu não acho que uma extensão seja apropriada aqui - é um caso um pouco especial e uma extensão se aplicaria a todas as strings. Se você realmente quisesse fazer isso, seria uma mudança trivial.
Keith

7
Que tal Enum.TryParse?
Elaine

15
muito agradável. você precisa de um where T: struct no seu último exemplo.
bbrik

331

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

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

Isso pode ser simplificado ainda mais com o tipo de parâmetro do C # 7.0 embutido :

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

45
Adicione o parâmetro booleano do meio para diferenciar maiúsculas de minúsculas e esta é a solução mais segura e elegante de longe.
DanM7 28/02

18
Vamos lá, quantos de vocês implementaram a resposta selecionada de 2008 para rolar para baixo e descobrir que essa é a melhor resposta (moderna)?
TEK

@TEK Na verdade, prefiro a resposta de 2008.
Zero3

Eu não entendo. Parsegera exceções explicativos para o que deu errado com a conversão (valor foi null, vazio ou não correspondente constante enum), que é muito melhor do que TryParse's valor de retorno booleano (que suprime o erro de concreto)
yair

2
Enum.TryParse <T> (String, T) é defeituoso ao analisar cadeias inteiras. Por exemplo, esse código analisará com êxito uma sequência sem sentido como uma enumeração sem sentido: var result = Enum.TryParse<System.DayOfWeek>("55", out var parsedEnum);
Mass Dot Net

196

Observe que o desempenho de Enum.Parse()é péssimo, porque é implementado via reflexão. (O mesmo vale paraEnum.ToString que acontece na outra direção.)

Se você precisar converter seqüências de caracteres para Enums em códigos sensíveis ao desempenho, sua melhor aposta é criar um Dictionary<String,YourEnum>na inicialização e usá-lo para fazer suas conversões.


10
Eu medi 3ms para converter uma string em um Enum na primeira execução, em um computador desktop. (Apenas para ilustrar o nível de inveja).
Matthieu Charbonnier

12
Wow 3ms é uma ordem de magnitude terrível
John Stock

1
você pode adicionar um exemplo de código de contornar isso, então temos uma idéia sobre como substituir e uso
transformador de

Se o seu aplicativo for usado por 1 milhão de pessoas => ele adiciona até 50 horas de vida humana que você está consumindo :) Em um único uso da página. : P
Cătălin Rădoi


31

Você pode usar métodos de extensão agora:

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

E você pode chamá-los pelo código abaixo (aqui, FilterTypeé um tipo de enumeração):

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

1
Eu atualizei isso para pegar o valor como objeto e convertê-lo em string dentro deste método. Dessa forma, eu posso pegar um valor int .ToEnum em vez de apenas strings.
RealSollyM 14/02

2
@SollyM Eu diria que é uma péssima idéia, pois esse método de extensão será aplicado a todos os tipos de objetos. Dois métodos de extensão, um para string e outro para int, seriam mais limpos e muito mais seguros na minha opinião.
Svish

@ Skish, isso é verdade. A única razão pela qual fiz isso é porque nosso código é usado apenas internamente e eu queria evitar escrever duas extensões. E como a única vez que convertemos para o Enum é com string ou int, não achei que fosse um problema.
RealSollyM

3
@SollyM Interno ou não, ainda sou eu quem mantém e usa meu código: PI ficaria irritado se eu pegasse um ToEnum em todos os menus do intellisense e, como você diz, desde que a única vez em que você converte em enum é de string ou int, você pode ter certeza de que precisará apenas desses dois métodos. E dois métodos não são muito mais do que um, especialmente quando são pequenos e do tipo utilitário: P
Svish

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

Portanto, se você tivesse um enum chamado mood, ficaria assim:

   enum Mood
   {
      Angry,
      Happy,
      Sad
   } 

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

18

CUIDADO:

enum Example
{
    One = 1,
    Two = 2,
    Three = 3
}

Enum.(Try)Parse() aceita vários argumentos separados por vírgula e os combina com 'ou' binário| . Você não pode desativar isso e, na minha opinião, quase nunca o deseja.

var x = Enum.Parse("One,Two"); // x is now Three

Mesmo se Threenão fosse definido, xainda obteria o valor int 3. Pior ainda: Enum.Parse () pode fornecer um valor que nem está definido para a enumeração!

Eu não gostaria de experimentar as conseqüências dos usuários, voluntária ou involuntariamente, desencadeando esse comportamento.

Além disso, como mencionado por outros, o desempenho é inferior ao ideal para enums grandes, ou seja, linear no número de valores possíveis.

Sugiro o seguinte:

    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
De fato, é muito útil saber disso Enum.(Try)Parse accepts multiple, comma-separated arguments, and combines them with binary 'or'. Significa que você pode configurar seus valores de enum como potências de 2 e você tem uma maneira muito fácil de analisar vários sinalizadores booleanos, por exemplo. "UseSSL, NoRetries, Sync". De fato, é para isso que foi projetado.
pcdev 22/08/18

16

Enum.Parse é seu amigo:

StatusEnum MyStatus = (StatusEnum)Enum.Parse(typeof(StatusEnum), "Active");

13

Você pode estender a resposta aceita com um valor padrão para evitar exceções:

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

Então você chama assim:

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

Se o valor padrão não for uma enumeração, o Enum.TryParse falhará e lançará uma exceção capturada.

Depois de anos usando essa função em nosso código em muitos lugares, talvez seja bom adicionar as informações de que essa operação custa desempenho!


Eu não gosto de valores padrão. Pode levar a resultados imprevisíveis.
Daniël Tulp 26/05

5
quando isso vai lançar uma exceção?
28716 andleer

@andleer se o valor de enum não se encaixar no mesmo tipo de enum que o valor padrão
Nelly

@ Nelly Código antigo aqui, mas o tipo defaultValuee o tipo de retorno do método são do tipo T. Se os tipos forem diferentes, você receberá um erro em tempo de compilação: "não é possível converter de 'ConsoleApp1.Size' para 'ConsoleApp1.Color'" ou quaisquer que sejam seus tipos.
andleer 7/01

@ Andleer, me desculpe, minha última resposta para você não estava correta. É possível que esse método gere uma Syste.ArgumentException no caso de alguém chamar essa função com um valor padrão que não seja do tipo enum. Com o c # 7.0, eu não conseguia criar uma cláusula where de T: Enum. É por isso que peguei essa possibilidade com uma tentativa de captura.
Nelly

8

Não podíamos assumir entradas perfeitamente válidas e acompanhamos essa variação da resposta de @ 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

Analisa a string no TEnum sem o método try / catch e sem o método TryParse () do .NET 4.5

/// <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
Se é necessário fazer uma descrição se o código já contiver uma descrição? Ok, eu fiz isso :)
jite.gs 30/10

3

Código super simples usando o TryParse:

var value = "Active";

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

2

Eu gosto da solução do método de extensão ..

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

Aqui abaixo minha implementação com testes.

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

==================== Um programa completo ====================

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

Eu usei classe (versão fortemente tipada do Enum com melhorias de análise e desempenho). Encontrei no GitHub e também deve funcionar no .NET 3.5. Ele possui alguma sobrecarga de memória, pois armazena em buffer um dicionário.

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

O post do blog é Enums - melhor sintaxe, melhor desempenho e TryParse no NET 3.5 .

E código: https://github.com/damieng/DamienGKit/blob/master/CSharp/DamienG.Library/System/EnumT.cs


1

Para desempenho, isso pode ajudar:

    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

Descobri que aqui o caso com valores de enumeração com valor de EnumMember não foi considerado. Aqui vamos nos:

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

E exemplo dessa enumeração:

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

1

Você precisa usar Enum.Parse para obter o valor do objeto de Enum, depois disso, é necessário alterar o valor do objeto para um valor específico de enum. A conversão para o valor de enum pode ser feita usando Convert.ChangeType. Veja o seguinte snippet de código

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

1

Experimente este exemplo:

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

Nesta amostra, você pode enviar todas as strings e definir seu Enum. Se você Enumtinha os dados desejados, retorne-os como seu Enumtipo.


1
Você está substituindo newModelem cada linha; portanto, se ele contiver traços, não será substituído. Além disso, você não tem que verificar se a cadeia contém qualquer coisa, você pode simplesmente chamar Replacede qualquer maneira:var newModel = model.Replace("-", "").Replace(" ", "");
Lars Kristensen

@LarsKristensen Sim, podemos criar um método que remova caracteres não alfanuméricos.
AmirReza-Farahlagha 01/10/19

1

Não tenho certeza quando isso foi adicionado, mas na classe Enum agora existe uma

Parse<TEnum>(stringValue)

Usado dessa maneira com o exemplo em questão:

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

ou ignorando o revestimento por:

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

Aqui estão os métodos descompilados usados:

    [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

Se o nome da propriedade for diferente do que você deseja chamá-lo (ou seja, diferenças de idioma), faça o seguinte:

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
    }     
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.