Como converter de System.Enum para um inteiro de base?

93

Eu gostaria de criar um método genérico para converter qualquer tipo derivado de System.Enum em seu valor inteiro correspondente, sem converter e, de preferência, sem analisar uma string.

Por exemplo, o que eu quero é algo assim:

// Trivial example, not actually what I'm doing.
class Converter
{
    int ToInteger(System.Enum anEnum)
    {
        (int)anEnum;
    }
}

Mas isso não parece funcionar. Resharper relata que você não pode converter uma expressão do tipo 'System.Enum' para o tipo 'int'.

Agora eu vim com essa solução, mas prefiro algo mais eficiente.

class Converter
{
    int ToInteger(System.Enum anEnum)
    {
        return int.Parse(anEnum.ToString("d"));
    }
}

Alguma sugestão?

orj
fonte
1
Eu acredito que é o compilador que está reclamando, não o Resharper.
Kugel
1
Não necessariamente. Eu tenho um método de extensão em System.Enum e, ocasionalmente, Resharper decide reclamar: Não é possível converter o tipo de argumento de instância 'Some.Cool.Type.That.Is.An.Enum' para 'System.Enum' quando é inquestionavelmente um enum. Se eu compilar e executar o código, ele funcionará perfeitamente. Se eu encerrar o VS, eliminar o cache do Resharper e ativá-lo novamente, tudo ficará bem depois de concluída a verificação. Para mim, é algum tipo de problema de cache. Pode ser o mesmo para ele.
Mir
@Mir Eu ouvi ReSharper "reclamar" disso também. A mesma correção para mim. Não sei por que confunde esses tipos, mas definitivamente não é o compilador.
akousmata

Respostas:

134

Se você não quiser lançar,

Convert.ToInt32()

poderia fazer o truque.

O cast direto (via (int)enumValue) não é possível. Note-se que isso também seria "perigoso", já que uma enumeração pode ter diferentes tipos subjacentes ( int, long, byte...).

Mais formalmente: System.Enumnão tem relação de herança direta com Int32(embora ambos sejam ValueTypes), então a conversão explícita não pode ser correta dentro do sistema de tipo

MartinStettner
fonte
2
Converter.ToInteger(MyEnum.MyEnumConstant);não dará nenhum erro aqui. Edite essa parte.
nawfal
Obrigado, @nawfal, você está certo. Editei a resposta (e aprendi algo novo :) ...)
MartinStettner
Não está funcionando se o tipo subjacente for diferente deint
Alex Zhukovskiy
@YaroslavSivakov não funcionará, por exemplo, para longenum.
Alex Zhukovskiy
System.Enumé uma classe, portanto, é um tipo de referência. Os valores provavelmente estão em caixas.
Teejay
42

Comecei a trabalhar lançando para um objeto e depois para um int:

public static class EnumExtensions
{
    public static int ToInt(this Enum enumValue)
    {
        return (int)((object)enumValue);
    }
}

Isso é feio e provavelmente não é a melhor maneira. Vou continuar mexendo nisso, para ver se consigo encontrar algo melhor ...

EDIT: Estava prestes a postar que Convert.ToInt32 (enumValue) funciona bem, e percebi que MartinStettner chegou antes de mim.

public static class EnumExtensions
{
    public static int ToInt(this Enum enumValue)
    {
        return Convert.ToInt32(enumValue);
    }
}

Teste:

int x = DayOfWeek.Friday.ToInt();
Console.WriteLine(x); // results in 5 which is int value of Friday

EDIT 2: Nos comentários, alguém disse que isso só funciona em C # 3.0. Acabei de testar isso no VS2005 assim e funcionou:

public static class Helpers
{
    public static int ToInt(Enum enumValue)
    {
        return Convert.ToInt32(enumValue);
    }
}

    static void Main(string[] args)
    {
        Console.WriteLine(Helpers.ToInt(DayOfWeek.Friday));
    }
BFree
fonte
Eu dei a você +1, mas esta solução vai funcionar apenas com C # 3.0 e superior.
Vadim
Eu acho que isso só satisfaz o compilador. Você conseguiu testar isso em tempo de execução? Não consegui passar nenhum valor para esta função ...
MartinStettner
Porque é um método de extensão? Ou há algo diferente sobre enums em versões mais antigas do C #?
BFree
Eu adicionei um teste ao final da minha postagem. Eu tentei isso e funcionou para mim.
BFree
@BFree: Parece funcionar apenas com métodos de extensão. Eu tentei com uma função "estilo antigo" (em C # 2.0) e não consegui encontrar a sintaxe para realmente passar nenhum valor para ela (é sobre isso que meu comentário anterior era)
MartinStettner
14

Se precisar converter qualquer enum em seu tipo subjacente (nem todos os enums são apoiados por int), você pode usar:

return System.Convert.ChangeType(
    enumValue,
    Enum.GetUnderlyingType(enumValue.GetType()));
Drew Noakes
fonte
5
Esta é a única resposta à prova de balas.
Rudey
É esta a causa do boxing unboxing?
b.ben
@ b.ben sim. Convert.ChangeTypeaceita objectcomo primeiro argumento e retorna object.
Drew Noakes em
Sim, eu realmente concordo com @Rudey sobre isso, de qualquer forma, você deve fazer testes de desempenho. A solução da BFree é de longe a mais rápida. Cerca de oito vezes mais rápido. De qualquer forma, ainda estamos falando sobre carrapatos.
Vinte
10

Por que você precisa reinventar a roda com um método auxiliar? É perfeitamente legal lançar um enumvalor para seu tipo subjacente.

É menos digitação e na minha opinião mais legível para usar ...

int x = (int)DayOfWeek.Tuesday;

... em vez de algo como ...

int y = Converter.ToInteger(DayOfWeek.Tuesday);
// or
int z = DayOfWeek.Tuesday.ToInteger();
LukeH
fonte
6
Quero converter QUALQUER valor de enum. Ou seja, tenho uma variedade de classes com campos enum que estão sendo processados ​​por algum código de forma genérica. É por isso que uma conversão simples para int não é apropriada.
orj
@orj, não tenho certeza do que você quer dizer. Você pode dar um exemplo mostrando o uso que você precisa?
LukeH
2
Às vezes, você não pode lançar diretamente um enum, como no caso de um método genérico. Como os métodos genéricos não podem ser restritos a enums, você deve restringi-los a algo como struct, que não pode ser convertido em um int. Nesse caso, você pode usar Convert.ToInt32 (enum) para fazer isso.
Daniel T.
Gosto da ideia de que o método de extensão ToInteger () segue o mesmo formato de ToString (). Eu acho que é menos legível converter para int por um lado quando você quer o valor int e usar ToString () quando precisamos do valor da string, especialmente quando o código está lado a lado.
Louise Eggleton
Além disso, o uso de um método de extensão pode adicionar consistência à sua base de código. Visto que, como @nawfal apontou, existem várias maneiras de obter o valor int de um enum, criar um método de extensão e impor seu uso significa que toda vez que você obtém o valor int de um enum, está usando o mesmo método
Louise Eggleton
4

Da minha resposta aqui :

Dado ecomo em:

Enum e = Question.Role;

Então estes funcionam:

int i = Convert.ToInt32(e);
int i = (int)(object)e;
int i = (int)Enum.Parse(e.GetType(), e.ToString());
int i = (int)Enum.ToObject(e.GetType(), e);

Os dois últimos são completamente feios. O primeiro deve ser mais legível, embora o segundo seja muito mais rápido. Ou pode ser que um método de extensão seja o melhor, o melhor dos dois mundos.

public static int GetIntValue(this Enum e)
{
    return e.GetValue<int>();
}

public static T GetValue<T>(this Enum e) where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
    return (T)(object)e;
}

Agora você pode ligar para:

e.GetValue<int>(); //or
e.GetIntValue();
Nawfal
fonte
Tem certeza que o segundo é mais rápido? Presumo que encaixe o enum e, em seguida, retire a caixa para int?
yoyo
1
@yoyo a evariável já tem a instância em caixa do tipo de valor real, ou seja, enum. Quando você escreve Enum e = Question.Role;, já está encaixotado. A questão é sobre como converter o tipo de caixa de System.Enumvolta para o tipo int subjacente (unboxing). Portanto, a unboxing é apenas a questão do desempenho. (int)(object)eé uma chamada desencaixotada direta; sim deve ser mais rápido do que outras abordagens. Veja isto
nawfal
obrigada pelo esclarecimento. Tive a impressão errada de que uma instância de System.Enum era um valor não encaixotado. Veja também - msdn.microsoft.com/en-us/library/aa691158(v=vs.71).aspx
yoyo
@yoyo hmm sim. A citação relevante do link: De qualquer tipo de enum para o tipo System.Enum.
nawfal
1

Transmitir de um System.Enumpara um intfunciona bem para mim (também está no MSDN ). Talvez seja um bug do Resharper.

jpoh
fonte
1
Qual versão do runtime você está usando?
JoshBerke
2
o link mostra o elenco de subtipos de System.Enum, não System.Enumele mesmo.
nawfal
Um problema semelhante que tive foi um problema de cache do Resharper. Fechar o VS e excluir o cache de Resharper fez com que o erro desaparecesse, mesmo após uma nova verificação completa.
Mir
1

Como os Enums são restritos a byte, sbyte, short, ushort, int, uint, long e ulong, podemos fazer algumas suposições.

Podemos evitar exceções durante a conversão usando o maior contêiner disponível. Infelizmente, qual recipiente usar não está claro porque ulong explodirá para números negativos e long irá explodir para números entre long.MaxValue e ulong.MaxValue. Precisamos alternar entre essas opções com base no tipo subjacente.

Claro, você ainda precisa decidir o que fazer quando o resultado não couber dentro de um int. Acho que o elenco está bem, mas ainda existem algumas pegadinhas:

  1. para enums com base em um tipo com um espaço de campo maior que int (long e ulong), é possível que alguns enums sejam avaliados com o mesmo valor.
  2. lançar um número maior que int.MaxValue lançará uma exceção se você estiver em uma região marcada.

Aqui está minha sugestão, vou deixar para o leitor decidir onde expor esta função; como ajudante ou extensão.

public int ToInt(Enum e)
{
  unchecked
  {
    if (e.GetTypeCode() == TypeCode.UInt64)
      return (int)Convert.ToUInt64(e);
    else
      return (int)Convert.ToInt64(e);
  }
}
eisenpony
fonte
-1

Não se esqueça de que o próprio tipo Enum contém várias funções auxiliares estáticas. Se tudo o que você deseja fazer é converter uma instância do enum em seu tipo inteiro correspondente, a conversão é provavelmente a maneira mais eficiente.

Acho que ReSharper está reclamando porque Enum não é uma enumeração de nenhum tipo específico, e as próprias enumerações derivam de um tipo de valor escalar, não Enum. Se você precisar de elenco adaptável de uma forma genérica, eu diria que isso pode servir para você (observe que o próprio tipo de enumeração também está incluído no genérico:

public static EnumHelpers
{
    public static T Convert<T, E>(E enumValue)
    {
        return (T)enumValue;
    }
}

Isso poderia então ser usado da seguinte forma:

public enum StopLight: int
{
    Red = 1,
    Yellow = 2,
    Green = 3
}

// ...

int myStoplightColor = EnumHelpers.Convert<int, StopLight>(StopLight.Red);

Não posso dizer com certeza de cara, mas o código acima pode até ser compatível com a inferência de tipo do C #, permitindo o seguinte:

int myStoplightColor = EnumHelpers.Convert<int>(StopLight.Red);
jrista
fonte
1
Infelizmente, em seu exemplo, E pode ser de qualquer tipo. Os genéricos não me permitem usar Enum como restrição de parâmetro de tipo. Não posso dizer: onde T: Enum
Vadim
Você poderia usar where T: struct, no entanto. Não é tão rígido quanto você deseja, mas eliminaria qualquer tipo de referência (o que eu acho que é o que você está tentando fazer.)
jrista
você pode usar: if (! typeof (E) .IsEnum) throw new ArgumentException ("E deve ser um tipo enumerado");
diadiora
1
Isso não é nem remotamente correto, o helper estático nem mesmo é válido.
Nicholi
1
Por que alguém deveria usar em EnumHelpers.Convert<int, StopLight>(StopLight.Red);vez de (int)StopLight.Read;? Também a questão está falando System.Enum.
nawfal