Criar método genérico que restringe T a um enum

1189

Estou construindo uma função para estender o Enum.Parseconceito que

  • Permite que um valor padrão seja analisado caso um valor de Enum não seja encontrado
  • Não diferencia maiúsculas de minúsculas

Então eu escrevi o seguinte:

public static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
{
    if (string.IsNullOrEmpty(value)) return defaultValue;
    foreach (T item in Enum.GetValues(typeof(T)))
    {
        if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
    }
    return defaultValue;
}

Estou recebendo uma restrição de erro não pode ser de classe especial System.Enum.

É justo, mas existe uma solução alternativa para permitir uma enumeração genérica ou terei de imitar a Parsefunção e passar um tipo como um atributo, o que força o requisito de boxe feio ao seu código.

EDITAR Todas as sugestões abaixo foram muito apreciadas, obrigado.

Já decidi (deixei o loop para manter a distinção entre maiúsculas e minúsculas - estou usando isso ao analisar XML)

public static class EnumUtils
{
    public static T ParseEnum<T>(string value, T defaultValue) where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum) throw new ArgumentException("T must be an enumerated type");
        if (string.IsNullOrEmpty(value)) return defaultValue;

        foreach (T item in Enum.GetValues(typeof(T)))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

EDIT: (16 de fev de 2015) Julien Lebosquain postou recentemente uma solução genérica de segurança de tipo imposta pelo compilador no MSIL ou F # abaixo, que vale a pena dar uma olhada e ser votada. Removerei esta edição se a solução surgir na parte superior da página.

johnc
fonte
10
Talvez você deve usar ToUpperInvariant () em vez de ToLower () ...
Max Galkin
31
@ Shimmy: Assim que você passa um tipo de valor para o método de extensão, você está trabalhando em uma cópia dele, para não poder alterar seu estado.
Garo Yeriazarian
4
Sei que é um encadeamento antigo, não sei se eles mudaram as coisas, mas os métodos de extensão funcionam bem para tipos de valor, com certeza nem sempre fazem tanto sentido, mas eu usei "TimeSpan Seconds estático público (int int) { retornar TimeSpan.FromSeconds (x);} "para habilitar a sintaxe de" Wait.For (5.Seconds ()) ... ""
Jens
6
Que isso não fazia parte da pergunta, mas você poderia melhorar a sua lógica de loop foreach usando String.Equals com StringComparison.InvariantCultureIgnoreCase
Firestrand

Respostas:

1006

Como o EnumType implementa a IConvertibleinterface, uma melhor implementação deve ser algo como isto:

public T GetEnumFromString<T>(string value) where T : struct, IConvertible
{
   if (!typeof(T).IsEnum) 
   {
      throw new ArgumentException("T must be an enumerated type");
   }

   //...
}

Isso ainda permitirá a passagem de tipos de valor implementados IConvertible. As chances são raras embora.

Vivek
fonte
2
Os genéricos estão disponíveis desde o .NET 2.0. Portanto, eles estão disponíveis no vb 2005 também.
Vivek
46
Bem, torne-o ainda mais restrito, se você optar por seguir esse caminho ... use "classe TestClass <T> em que T: struct, IComparable, IFormattable, IConvertible"
Ricardo Nolde
106
Outra sugestão é definir o tipo genérico com o identificador TEnum. Assim: public TEnum GetEnumFromString <TEnum> (valor string) onde TEnum: struct, IConvertible, IComparible, IFormattable {}
Lisa
11
Você não ganha muito ao incluir as outras interfaces, porque quase todos os tipos de valores internos implementam todas essas interfaces. Isso é especialmente verdadeiro para restrições em um método de extensão genérico, extremamente útil para operar enumerações, exceto pelo fato de que esses métodos de extensão são como um vírus que infecta todos os seus objetos. IConvertable, pelo menos, reduz bastante.
russbishop
2
@ SamIam: Quando você postou, este tópico tinha 6 anos e meio e você estava correto, sem verificação em tempo de compilação em nenhuma das respostas. Então, apenas 3 dias depois, após 6 anos, você conseguiu seu desejo - veja a publicação de Julien Lebosquain abaixo.
David I. McIntosh
663

Este recurso é finalmente suportado no C # 7.3!

O seguinte snippet (das amostras dotnet ) demonstra como:

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Certifique-se de definir sua versão do idioma no seu projeto C # para a versão 7.3.


Resposta original abaixo:

Estou atrasado para o jogo, mas aceitei como um desafio ver como isso poderia ser feito. Não é possível em C # (ou VB.NET, mas role para baixo para F #), mas é possível em MSIL. Eu escrevi essa coisinha ....

// license: http://www.apache.org/licenses/LICENSE-2.0.html
.assembly MyThing{}
.class public abstract sealed MyThing.Thing
       extends [mscorlib]System.Object
{
  .method public static !!T  GetEnumFromString<valuetype .ctor ([mscorlib]System.Enum) T>(string strValue,
                                                                                          !!T defaultValue) cil managed
  {
    .maxstack  2
    .locals init ([0] !!T temp,
                  [1] !!T return_value,
                  [2] class [mscorlib]System.Collections.IEnumerator enumerator,
                  [3] class [mscorlib]System.IDisposable disposer)
    // if(string.IsNullOrEmpty(strValue)) return defaultValue;
    ldarg strValue
    call bool [mscorlib]System.String::IsNullOrEmpty(string)
    brfalse.s HASVALUE
    br RETURNDEF         // return default it empty

    // foreach (T item in Enum.GetValues(typeof(T)))
  HASVALUE:
    // Enum.GetValues.GetEnumerator()
    ldtoken !!T
    call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
    call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
    callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator() 
    stloc enumerator
    .try
    {
      CONDITION:
        ldloc enumerator
        callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
        brfalse.s LEAVE

      STATEMENTS:
        // T item = (T)Enumerator.Current
        ldloc enumerator
        callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
        unbox.any !!T
        stloc temp
        ldloca.s temp
        constrained. !!T

        // if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        callvirt instance string [mscorlib]System.Object::ToString()
        callvirt instance string [mscorlib]System.String::ToLower()
        ldarg strValue
        callvirt instance string [mscorlib]System.String::Trim()
        callvirt instance string [mscorlib]System.String::ToLower()
        callvirt instance bool [mscorlib]System.String::Equals(string)
        brfalse.s CONDITION
        ldloc temp
        stloc return_value
        leave.s RETURNVAL

      LEAVE:
        leave.s RETURNDEF
    }
    finally
    {
        // ArrayList's Enumerator may or may not inherit from IDisposable
        ldloc enumerator
        isinst [mscorlib]System.IDisposable
        stloc.s disposer
        ldloc.s disposer
        ldnull
        ceq
        brtrue.s LEAVEFINALLY
        ldloc.s disposer
        callvirt instance void [mscorlib]System.IDisposable::Dispose()
      LEAVEFINALLY:
        endfinally
    }

  RETURNDEF:
    ldarg defaultValue
    stloc return_value

  RETURNVAL:
    ldloc return_value
    ret
  }
} 

O que gera uma função que se pareceria com isso, se fosse C # válido:

T GetEnumFromString<T>(string valueString, T defaultValue) where T : Enum

Em seguida, com o seguinte código C #:

using MyThing;
// stuff...
private enum MyEnum { Yes, No, Okay }
static void Main(string[] args)
{
    Thing.GetEnumFromString("No", MyEnum.Yes); // returns MyEnum.No
    Thing.GetEnumFromString("Invalid", MyEnum.Okay);  // returns MyEnum.Okay
    Thing.GetEnumFromString("AnotherInvalid", 0); // compiler error, not an Enum
}

Infelizmente, isso significa ter essa parte do seu código escrita em MSIL em vez de C #, com o único benefício adicional de poder restringir esse método System.Enum . Também é meio chato, porque é compilado em uma montagem separada. No entanto, isso não significa que você deve implantá-lo dessa maneira.

Removendo a linha .assembly MyThing{}e chamando o ilasm da seguinte maneira:

ilasm.exe /DLL /OUTPUT=MyThing.netmodule

você obtém um módulo de rede em vez de um assembly.

Infelizmente, o VS2010 (e anteriormente, obviamente) não oferece suporte à adição de referências de módulo de rede, o que significa que você teria que deixá-lo em dois assemblies separados quando estiver depurando. A única maneira de adicioná-los como parte do seu assembly seria executar o csc.exe você mesmo, usando o /addmodule:{files}argumento da linha de comando. Não seria muito doloroso em um script MSBuild. Obviamente, se você é corajoso ou estúpido, pode executar o CSC manualmente manualmente toda vez. E certamente fica mais complicado, pois vários conjuntos precisam acessar.

Então, isso pode ser feito em .Net. Vale a pena o esforço extra? Bem, acho que vou deixar você decidir sobre isso.


Solução F # como alternativa

Crédito extra: Acontece que uma restrição genérica enumé possível em pelo menos uma outra linguagem .NET além de MSIL: F #.

type MyThing =
    static member GetEnumFromString<'T when 'T :> Enum> str defaultValue: 'T =
        /// protect for null (only required in interop with C#)
        let str = if isNull str then String.Empty else str

        Enum.GetValues(typedefof<'T>)
        |> Seq.cast<_>
        |> Seq.tryFind(fun v -> String.Compare(v.ToString(), str.Trim(), true) = 0)
        |> function Some x -> x | None -> defaultValue

Essa é mais fácil de manter, pois é uma linguagem conhecida com suporte completo ao IDE do Visual Studio, mas você ainda precisa de um projeto separado em sua solução. No entanto, produz naturalmente uma IL consideravelmente diferente (o código é muito diferente) e depende daFSharp.Core biblioteca, que, como qualquer outra biblioteca externa, precisa se tornar parte da sua distribuição.

Veja como você pode usá-lo (basicamente o mesmo que a solução MSIL) e para mostrar que ele falha corretamente em estruturas sinônimas:

// works, result is inferred to have type StringComparison
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", StringComparison.Ordinal);
// type restriction is recognized by C#, this fails at compile time
var result = MyThing.GetEnumFromString("OrdinalIgnoreCase", 42);
Christopher Currens
fonte
67
Sim, muito hardcore. Tenho o maior respeito por alguém que pode codificar em IL e sei como os recursos são suportados no nível de idioma mais alto - um nível que muitos de nós ainda consideramos baixo em aplicativos, regras de negócios, UIs, bibliotecas de componentes etc. #
TonyG
13
O que eu realmente gostaria de saber é por que a equipe de C # ainda não começou a permitir isso, pois já é suportada pelo MSIL.
MgSam 19/09/12
25
@MgSam - De Eric Lippert :There's no particularly unusual reason why not; we have lots of other things to do, limited budgets, and this one has never made it past the "wouldn't this be nice?" discussion in the language design team.
Christopher Currens
5
@ LordofScripts: Eu acho que a razão é que, uma vez que uma classe que restringe Ta System.Enumnão seria capaz de fazer todas as coisas Tque as pessoas esperavam, os autores do C # acharam que eles também poderiam proibi-la completamente. Considero a decisão infeliz, uma vez que o C # simplesmente ignorou qualquer tratamento especial de System.Enumrestrições, seria possível escrever um HasAnyFlags<T>(this T it, T other)método de extensão com ordens de magnitude mais rápidas do que Enum.HasFlag(Enum)e que verificassem seus argumentos com tipo.
supercat
9
Acho que nunca tive um projeto em que não acabei aqui. C # 6 é 110% de açúcar sintático e ISTO não entrou? Chega de besteira.
Michael Blackburn
214

C # ≥ 7,3

A partir do C # 7.3 (disponível no Visual Studio 2017 ≥ v15.7), este código agora é completamente válido:

public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, Enum
{
 ...
}

C # ≤ 7,2

Você pode ter uma restrição de enum imposta pelo compilador real abusando da herança de restrição. O seguinte código especifica tanto um classe um structconstrangimentos ao mesmo tempo:

public abstract class EnumClassUtils<TClass>
where TClass : class
{

    public static TEnum Parse<TEnum>(string value)
    where TEnum : struct, TClass
    {
        return (TEnum) Enum.Parse(typeof(TEnum), value);
    }

}

public class EnumUtils : EnumClassUtils<Enum>
{
}

Uso:

EnumUtils.Parse<SomeEnum>("value");

Nota: isso é especificamente indicado na especificação de idioma do C # 5.0:

Se o parâmetro do tipo S depende do parâmetro do tipo T, é [...] válido: S ter a restrição de tipo de valor e T ter a restrição de tipo de referência. Efetivamente, isso limita T aos tipos System.Object, System.ValueType, System.Enum e qualquer tipo de interface.

Julien Lebosquain
fonte
7
@ DavidI.McIntosh EnumClassUtils<System.Enum>é suficiente para restringir T a todo System.Enume qualquer tipo derivado. structon Parserestringe ainda mais a um tipo de enum real. Você precisa restringir a Enumem algum momento. Para fazer isso, sua classe precisa ser aninhada. Veja gist.github.com/MrJul/7da12f5f2d6c69f03d79
Julien Lebosquain
7
Só para esclarecer, meu comentário "não agradável" não foi um comentário sobre sua solução - é realmente um truque bonito. Apenas "não é agradável" que a MS nos obriga a usar um hack tão complicado.
David I. McIntosh
2
Existe uma maneira de trabalhar isso para também ser útil para métodos de extensão?
Mord Zuber
3
O que a where TClass : classrestrição ganha aqui?
tsemer 4/01/17
2
@Trinkyoenum DefinitelyNotAnInt : byte { Realize, That, I, Am, Not, An, Int } enum AlsoNotAnInt : long { Well, Bummer }
M.Stramm
30

Editar

A pergunta foi agora respondida de maneira soberba por Julien Lebosquain . Eu também gostaria de estender sua resposta com ignoreCase, defaultValuee argumentos opcionais, enquanto a adição de TryParsee ParseOrDefault.

public abstract class ConstrainedEnumParser<TClass> where TClass : class
// value type constraint S ("TEnum") depends on reference type T ("TClass") [and on struct]
{
    // internal constructor, to prevent this class from being inherited outside this code
    internal ConstrainedEnumParser() {}
    // Parse using pragmatic/adhoc hard cast:
    //  - struct + class = enum
    //  - 'guaranteed' call from derived <System.Enum>-constrained type EnumUtils
    public static TEnum Parse<TEnum>(string value, bool ignoreCase = false) where TEnum : struct, TClass
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase);
    }
    public static bool TryParse<TEnum>(string value, out TEnum result, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        var didParse = Enum.TryParse(value, ignoreCase, out result);
        if (didParse == false)
        {
            result = defaultValue;
        }
        return didParse;
    }
    public static TEnum ParseOrDefault<TEnum>(string value, bool ignoreCase = false, TEnum defaultValue = default(TEnum)) where TEnum : struct, TClass // value type constraint S depending on T
    {
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum result;
        if (Enum.TryParse(value, ignoreCase, out result)) { return result; }
        return defaultValue;
    }
}

public class EnumUtils: ConstrainedEnumParser<System.Enum>
// reference type constraint to any <System.Enum>
{
    // call to parse will then contain constraint to specific <System.Enum>-class
}

Exemplos de uso:

WeekDay parsedDayOrArgumentException = EnumUtils.Parse<WeekDay>("monday", ignoreCase:true);
WeekDay parsedDayOrDefault;
bool didParse = EnumUtils.TryParse<WeekDay>("clubs", out parsedDayOrDefault, ignoreCase:true);
parsedDayOrDefault = EnumUtils.ParseOrDefault<WeekDay>("friday", ignoreCase:true, defaultValue:WeekDay.Sunday);

Velho

Minhas antigas melhorias na resposta de Vivek usando os comentários e os 'novos' desenvolvimentos:

  • usar TEnum para maior clareza para os usuários
  • adicione mais restrições de interface para verificação adicional de restrições
  • vamos TryParselidar ignoreCasecom o parâmetro existente (introduzido no VS2010 / .Net 4)
  • opcionalmente, use o defaultvalor genérico (introduzido no VS2005 / .Net 2)
  • use argumentos opcionais (introduzidos no VS2010 / .Net 4) com valores padrão, para defaultValueeignoreCase

resultando em:

public static class EnumUtils
{
    public static TEnum ParseEnum<TEnum>(this string value,
                                         bool ignoreCase = true,
                                         TEnum defaultValue = default(TEnum))
        where TEnum : struct,  IComparable, IFormattable, IConvertible
    {
        if ( ! typeof(TEnum).IsEnum) { throw new ArgumentException("TEnum must be an enumerated type"); }
        if (string.IsNullOrEmpty(value)) { return defaultValue; }
        TEnum lResult;
        if (Enum.TryParse(value, ignoreCase, out lResult)) { return lResult; }
        return defaultValue;
    }
}
Yahoo Serious
fonte
18

Você pode definir um construtor estático para a classe que verificará se o tipo T é uma enumeração e lançará uma exceção, se não for. Este é o método mencionado por Jeffery Richter em seu livro CLR via C #.

internal sealed class GenericTypeThatRequiresAnEnum<T> {
    static GenericTypeThatRequiresAnEnum() {
        if (!typeof(T).IsEnum) {
        throw new ArgumentException("T must be an enumerated type");
        }
    }
}

Em seguida, no método de análise, você pode apenas usar Enum.Parse (typeof (T), input, true) para converter de string em enum. O último parâmetro verdadeiro é para ignorar o caso da entrada.

Karg
fonte
1
Essa é uma boa opção para classes genéricas - mas é claro que não ajuda em métodos genéricos.
McGarnagle
Além disso, isso também não é imposto em tempo de compilação, você saberia apenas que forneceu um não Enum Tquando o construtor foi executado. Embora isso seja muito melhor do que esperar por um construtor de instância.
Jrh # 9/18
15

Também deve ser considerado que, uma vez que o lançamento do C # 7.3 usando restrições de Enum é suportado imediatamente, sem a necessidade de verificação e outras informações.

Então, seguindo em frente e dado que você alterou a versão do idioma do seu projeto para C # 7.3, o código a seguir funcionará perfeitamente:

    private static T GetEnumFromString<T>(string value, T defaultValue) where T : Enum
    {
        // Your code goes here...
    }

Caso você não saiba como alterar a versão do idioma para C # 7.3, veja a seguinte captura de tela: insira a descrição da imagem aqui

EDIT 1 - Versão necessária do Visual Studio e considerando ReSharper

Para o Visual Studio reconhecer a nova sintaxe, você precisa pelo menos da versão 15.7. Você pode encontrar isso também mencionado nas notas de versão da Microsoft, consulte Visual Studio 2017 15.7 Notas de versão . Obrigado a @MohamedElshawaf por apontar esta pergunta válida.

Observe também que, no meu caso, o ReSharper 2018.1 até o momento em que este EDIT foi escrito ainda não suporta C # 7.3. A ativação do ReSharper destaca a restrição de Enum como um erro dizendo que não é possível usar 'System.Array', 'System.Delegate', 'System.Enum', 'System.ValueType', 'object' como restrição de parâmetro de tipo . ReSharper sugere como uma solução rápida para remover a restrição 'Enum' do parâmetro parâmetro T do método

No entanto, se você desativar o ReSharper temporariamente em Ferramentas -> Opções -> ReSharper Ultimate -> Geral, verá que a sintaxe está perfeitamente correta, pois você usa o VS 15.7 ou superior e o C # 7.3 ou superior.

baumgarb
fonte
1
Qual versão do VS você está usando?
Mshwf 11/1118
1
@MohamedElshawaf Eu acredito que é a versão 15.7, que contém suporte para C # 7.3
Patrick Roberts
1
Eu acho que é melhor escrever where T : struct, Enum, para evitar passar a System.Enumsi próprio como parâmetro de tipo.
Mariusz Pawelski
Como @MariuszPawelski, eu escrevo struct, Enum. Minha lógica é explicada na resposta e nos comentários aqui .
Stephen Kennedy
As informações do ReSharper realmente me ajudaram. Observe que a última versão de visualização suporta esse recurso.
DalSoft 6/11
11

Modifiquei a amostra por dimarzionista. Esta versão funcionará apenas com Enums e não permitirá que as estruturas passem.

public static T ParseEnum<T>(string enumString)
    where T : struct // enum 
    {
    if (String.IsNullOrEmpty(enumString) || !typeof(T).IsEnum)
       throw new Exception("Type given must be an Enum");
    try
    {

       return (T)Enum.Parse(typeof(T), enumString, true);
    }
    catch (Exception ex)
    {
       return default(T);
    }
}
Bivoauc
fonte
13
Eu não retornaria o valor padrão em caso de falha; Eu deixaria a exceção se propagar (assim como acontece com Enum.Parse). Em vez disso, use TryParse retornando um bool e retorne o resultado usando um parâmetro out.
Mark Simpson
1
O OP quer não fazer distinção entre maiúsculas e minúsculas, não é.
Konrad Morawski
9

Tentei melhorar um pouco o código:

public T LoadEnum<T>(string value, T defaultValue = default(T)) where T : struct, IComparable, IFormattable, IConvertible
{
    if (Enum.IsDefined(typeof(T), value))
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
    return defaultValue;
}
Martin
fonte
1
Isso é melhor do que a resposta aceita, pois permite que você chame defaultValue.ToString("D", System.Globalization.NumberFormatInfo.CurrentInfo), embora não saiba qual é o tipo de enum, apenas que o objeto é um enum.
Styfle 02/11
1
A verificação antecipada IsDefinedirá arruinar a insensibilidade do caso. Ao contrário Parse, IsDefinednão tem ignoreCaseargumento, e o MSDN diz que apenas corresponde ao caso exato .
Nyerguds 6/03/19
5

Eu tenho um requisito específico no qual exigi usar enum com texto associado ao valor de enum. Por exemplo, quando eu uso enum para especificar o tipo de erro, é necessário descrever os detalhes do erro.

public static class XmlEnumExtension
{
    public static string ReadXmlEnumAttribute(this Enum value)
    {
        if (value == null) throw new ArgumentNullException("value");
        var attribs = (XmlEnumAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (XmlEnumAttribute), true);
        return attribs.Length > 0 ? attribs[0].Name : value.ToString();
    }

    public static T ParseXmlEnumAttribute<T>(this string str)
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            var attribs = (XmlEnumAttribute[])item.GetType().GetField(item.ToString()).GetCustomAttributes(typeof(XmlEnumAttribute), true);
            if(attribs.Length > 0 && attribs[0].Name.Equals(str)) return item;
        }
        return (T)Enum.Parse(typeof(T), str, true);
    }
}

public enum MyEnum
{
    [XmlEnum("First Value")]
    One,
    [XmlEnum("Second Value")]
    Two,
    Three
}

 static void Main()
 {
    // Parsing from XmlEnum attribute
    var str = "Second Value";
    var me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    // Parsing without XmlEnum
    str = "Three";
    me = str.ParseXmlEnumAttribute<MyEnum>();
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
    me = MyEnum.One;
    System.Console.WriteLine(me.ReadXmlEnumAttribute());
}
Sunny Rajwadi
fonte
4

Espero que isso seja útil:

public static TValue ParseEnum<TValue>(string value, TValue defaultValue)
                  where TValue : struct // enum 
{
      try
      {
            if (String.IsNullOrEmpty(value))
                  return defaultValue;
            return (TValue)Enum.Parse(typeof (TValue), value);
      }
      catch(Exception ex)
      {
            return defaultValue;
      }
}
dimarzionista
fonte
1
Se você precisar de insensibilidade ao caso, simplesmente substitua return (TValue)Enum.Parse(typeof (TValue), value);porreturn (TValue)Enum.Parse(typeof (TValue), value, true);
Paulo Santos
3

Curiosamente, aparentemente isso é possível em outros idiomas (C ++ gerenciado, diretamente IL).

Citar:

... Na verdade, ambas as restrições produzem IL válida e também podem ser consumidas pelo C # se escritas em outro idioma (você pode declarar essas restrições no C ++ gerenciado ou no IL).

Quem sabe

Andrew Backer
fonte
2
As extensões gerenciadas para C ++ não têm QUALQUER suporte para genéricos, acho que você quer dizer C ++ / CLI.
Ben Voigt
3

Esta é a minha opinião. Combinado a partir das respostas e do MSDN

public static TEnum ParseToEnum<TEnum>(this string text) where TEnum : struct, IConvertible, IComparable, IFormattable
{
    if (string.IsNullOrEmpty(text) || !typeof(TEnum).IsEnum)
        throw new ArgumentException("TEnum must be an Enum type");

    try
    {
        var enumValue = (TEnum)Enum.Parse(typeof(TEnum), text.Trim(), true);
        return enumValue;
    }
    catch (Exception)
    {
        throw new ArgumentException(string.Format("{0} is not a member of the {1} enumeration.", text, typeof(TEnum).Name));
    }
}

Origem do MSDN

KarmaEDV
fonte
2
Isso realmente não faz sentido. Se, TEnumna verdade, é um tipo de Enum, mas texté uma string vazia, você obtém o ArgumentExceptionditado "TEnum deve ser um tipo de Enum", mesmo que seja.
Nick
3

As respostas existentes são verdadeiras em C # <= 7.2. No entanto, há uma solicitação de recurso de linguagem C # (vinculada a uma solicitação de recurso corefx ) para permitir o seguinte;

public class MyGeneric<TEnum> where TEnum : System.Enum
{ }

No momento da redação, o recurso é "Em discussão" nas Reuniões de Desenvolvimento de Idiomas.

EDITAR

De acordo com as informações de nawfal , isso está sendo introduzido no C # 7.3 .

DiskJunky
fonte
1
Discussão interessante lá, obrigado. No entanto, nada
gravado
1
@ johnc, muito verdadeiro, mas vale uma nota e é um recurso frequentemente solicitado. Probabilidades justas sobre a entrada.
DiskJunky 27/03
1
Isso está chegando no C # 7.3: docs.microsoft.com/en-us/visualstudio/releasenotes/… . :)
Nawfal
1

Eu sempre gostei disso (você pode modificar conforme apropriado):

public static IEnumerable<TEnum> GetEnumValues()
{
  Type enumType = typeof(TEnum);

  if(!enumType.IsEnum)
    throw new ArgumentException("Type argument must be Enum type");

  Array enumValues = Enum.GetValues(enumType);
  return enumValues.Cast<TEnum>();
}
Jeff
fonte
1

Eu amei a solução de Christopher Currens usando IL, mas para aqueles que não querem lidar com negócios complicados de incluir o MSIL em seu processo de criação, escrevi uma função semelhante em C #.

Observe que você não pode usar restrições genéricas, where T : Enumporque o Enum é do tipo especial. Portanto, eu tenho que verificar se determinado tipo genérico é realmente enum.

Minha função é:

public static T GetEnumFromString<T>(string strValue, T defaultValue)
{
    // Check if it realy enum at runtime 
    if (!typeof(T).IsEnum)
        throw new ArgumentException("Method GetEnumFromString can be used with enums only");

    if (!string.IsNullOrEmpty(strValue))
    {
        IEnumerator enumerator = Enum.GetValues(typeof(T)).GetEnumerator();
        while (enumerator.MoveNext())
        {
            T temp = (T)enumerator.Current;
            if (temp.ToString().ToLower().Equals(strValue.Trim().ToLower()))
                return temp;
        }
    }

    return defaultValue;
}
especialista
fonte
1

Encapsulei a solução da Vivek em uma classe de utilitário que você pode reutilizar. Observe que você ainda deve definir restrições de tipo "onde T: struct, IConvertible" em seu tipo.

using System;

internal static class EnumEnforcer
{
    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your type parameter {0} is an enum.",
                typeParameterName,
                methodName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="typeParameterName">Name of the type parameter.</param>
    /// <param name="methodName">Name of the method which accepted the parameter.</param>
    /// <param name="inputParameterName">Name of the input parameter of this page.</param>
    public static void EnforceIsEnum<T>(string typeParameterName, string methodName, string inputParameterName)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            string message = string.Format(
                "Generic parameter {0} in {1} method forces an enumerated type. Make sure your input parameter {2} is of correct type.",
                typeParameterName,
                methodName,
                inputParameterName);

            throw new ArgumentException(message);
        }
    }

    /// <summary>
    /// Makes sure that generic input parameter is of an enumerated type.
    /// </summary>
    /// <typeparam name="T">Type that should be checked.</typeparam>
    /// <param name="exceptionMessage">Message to show in case T is not an enum.</param>
    public static void EnforceIsEnum<T>(string exceptionMessage)
        where T : struct, IConvertible
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException(exceptionMessage);
        }
    }
}
niaher
fonte
1

Eu criei uma extensão Método to get integer value from enum dê uma olhada na implementação do método

public static int ToInt<T>(this T soure) where T : IConvertible//enum
{
    if (typeof(T).IsEnum)
    {
        return (int) (IConvertible)soure;// the tricky part
    }
    //else
    //    throw new ArgumentException("T must be an enumerated type");
    return soure.ToInt32(CultureInfo.CurrentCulture);
}

isso é uso

MemberStatusEnum.Activated.ToInt()// using extension Method
(int) MemberStatusEnum.Activated //the ordinary way
Basheer AL-MOMANI
fonte
Embora provavelmente funcione, quase não tem relevância para a questão.
Quetzalcoatl
1

Como indicado em outras respostas anteriores; Embora isso não possa ser expresso no código-fonte, ele pode ser feito no nível IL. A resposta de Christopher Currens mostra como a IL faz isso.

Com Fody s Add-In ExtraConstraints.Fody há uma maneira muito simples, completo com build-ferramentas, para alcançar este objectivo. Basta adicionar os pacotes de nuget ( Fody, ExtraConstraints.Fody) ao seu projeto e adicionar as restrições da seguinte forma (trecho do Leia-me do ExtraConstraints):

public void MethodWithEnumConstraint<[EnumConstraint] T>() {...}

public void MethodWithTypeEnumConstraint<[EnumConstraint(typeof(ConsoleColor))] T>() {...}

e Fody adicionará a IL necessária para a restrição estar presente. Observe também o recurso adicional de restringir delegados:

public void MethodWithDelegateConstraint<[DelegateConstraint] T> ()
{...}

public void MethodWithTypeDelegateConstraint<[DelegateConstraint(typeof(Func<int>))] T> ()
{...}

Em relação ao Enums, você também pode anotar o Enums.NET altamente interessante .

BatteryBackupUnit
fonte
1

Esta é a minha implementação. Basicamente, você pode configurar qualquer atributo e ele funciona.

public static class EnumExtensions
    {
        public static string GetDescription(this Enum @enum)
        {
            Type type = @enum.GetType();
            FieldInfo fi = type.GetField(@enum.ToString());
            DescriptionAttribute[] attrs =
                fi.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[];
            if (attrs.Length > 0)
            {
                return attrs[0].Description;
            }
            return null;
        }
    }
Cubelaster
fonte
0

Se não há problema em usar a conversão direta posteriormente, acho que você pode usar a System.Enumclasse base em seu método, sempre que necessário. Você só precisa substituir os parâmetros de tipo com cuidado. Portanto, a implementação do método seria como:

public static class EnumUtils
{
    public static Enum GetEnumFromString(string value, Enum defaultValue)
    {
        if (string.IsNullOrEmpty(value)) return defaultValue;
        foreach (Enum item in Enum.GetValues(defaultValue.GetType()))
        {
            if (item.ToString().ToLower().Equals(value.Trim().ToLower())) return item;
        }
        return defaultValue;
    }
}

Então você pode usá-lo como:

var parsedOutput = (YourEnum)EnumUtils.GetEnumFromString(someString, YourEnum.DefaultValue);
uluorta
fonte
o uso de Enum.ToObject()produziria um resultado mais flexível. Alem de que, você poderia fazer as comparações de strings sem maiúsculas e minúsculas que negam a necessidade de chamarToLower()
DiskJunky
-6

Apenas para completar, a seguir é uma solução Java. Estou certo de que o mesmo poderia ser feito em C # também. Isso evita precisar especificar o tipo em qualquer lugar do código - em vez disso, você o especifica nas strings que você está tentando analisar.

O problema é que não há como saber qual enumeração a String pode corresponder - portanto, a resposta é resolver esse problema.

Em vez de aceitar apenas o valor da string, aceite uma String que tenha a enumeração e o valor no formato "enumeration.value". O código de trabalho está abaixo - requer Java 1.8 ou posterior. Isso também tornaria o XML mais preciso, pois você verá algo como color = "Color.red" em vez de apenas color = "red".

Você chamaria o método acceptEnumeratedValue () com uma sequência que contenha o nome da enumeração nome do valor do ponto.

O método retorna o valor enumerado formal.

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;


public class EnumFromString {

    enum NumberEnum {One, Two, Three};
    enum LetterEnum {A, B, C};


    Map<String, Function<String, ? extends Enum>> enumsByName = new HashMap<>();

    public static void main(String[] args) {
        EnumFromString efs = new EnumFromString();

        System.out.print("\nFirst string is NumberEnum.Two - enum is " + efs.acceptEnumeratedValue("NumberEnum.Two").name());
        System.out.print("\nSecond string is LetterEnum.B - enum is " + efs.acceptEnumeratedValue("LetterEnum.B").name());

    }

    public EnumFromString() {
        enumsByName.put("NumberEnum", s -> {return NumberEnum.valueOf(s);});
        enumsByName.put("LetterEnum", s -> {return LetterEnum.valueOf(s);});
    }

    public Enum acceptEnumeratedValue(String enumDotValue) {

        int pos = enumDotValue.indexOf(".");

        String enumName = enumDotValue.substring(0, pos);
        String value = enumDotValue.substring(pos + 1);

        Enum enumeratedValue = enumsByName.get(enumName).apply(value);

        return enumeratedValue;
    }


}
Rodney P. Barbati
fonte