Menu

Enum em C#: valores, ToString, Parse, Flags e como percorrer

Como os enums funcionam em C#: declarar constantes com nome, valores inteiros subjacentes e cast, converter um enum para string e uma string para enum com Parse e TryParse, listar todos os valores, [Flags] com operadores bit a bit e HasFlag, switch sobre um enum e tratamento de valores indefinidos.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um enum (enumeração) é um tipo cujos valores são um conjunto fixo de constantes com nome: status de pedidos, dias da semana, níveis de log. Por baixo, cada nome é um inteiro, mas o sistema de tipos impede que um OrderStatus seja confundido com um int comum ou com outro enum.

Declarando e usando um enum

Liste os nomes dos membros entre chaves. Por padrão, o primeiro é 0 e cada um seguinte é um a mais:

Saída:

Paid
On its way
True
2

O enum é um tipo de verdade: um método que recebe um OrderStatus não pode ser chamado com 3 nem com um LogLevel por engano. Enums são tipos de valor, então nunca são null e se comparam com == pelo valor.

Valores explícitos e o tipo subjacente

Você pode atribuir os números você mesmo. Isso importa sempre que o número sai do seu programa (uma coluna de banco de dados, um status HTTP, um formato de arquivo), porque então renumerar quebra os dados guardados:

Saída:

404
Created
418
1
Byte

Duas coisas a notar. O cast de um int para um enum nunca falha: (HttpStatus)418 é um valor válido que só não tem nome, e é impresso como o número. E o tipo subjacente pode ser qualquer tipo inteiro (byte, short, long, ...), o que só importa em código sensível a armazenamento; int é o padrão e quase sempre a escolha certa.

Quando você adicionar membros depois, adicione-os no fim ou dê valores explícitos. Inserir Refunded entre Paid e Shipped muda sem aviso o número de todos os membros seguintes.

Enum para string

ToString() retorna o nome do membro, que também é o que Console.WriteLine e a interpolação de strings usam. Strings de formato mudam a saída:

Saída:

Warning
2
00000002
[Warning]
Error
Error
Needs attention

Nomes de membros são identificadores, então não podem ter espaços e não são traduzidos. Para textos mostrados aos usuários, mapeie os valores você mesmo, como Label faz, ou com um Dictionary<LogLevel, string>. Alguns códigos colocam um atributo [Description("Needs attention")] em cada membro e o leem com reflection; a página de reflection e atributos mostra como essa consulta funciona.

String para enum: Parse e TryParse

Enum.Parse converte um nome de volta em um valor e lança ArgumentException se nada corresponder. Enum.TryParse retorna false no lugar, que é o que você quer para qualquer entrada que não controla:

Saída:

Large
Medium
Parse threw ArgumentException for Huge
small  parsed=True  value=Small  defined=True
XL     parsed=False value=Small  defined=True
2      parsed=True  value=Large  defined=True
7      parsed=True  value=7      defined=False

As duas últimas linhas são a armadilha. Os dois métodos aceitam strings numéricas, então "7" é convertido com sucesso para um Size que não tem nome. E um TryParse que falha define o resultado como 0, que aqui é o aparentemente válido Small. Quando o texto vem de uma query string, de um arquivo de configuração ou de um formulário, verifique sempre o valor de retorno e o Enum.IsDefined:

if (Enum.TryParse(input, true, out Size size) && Enum.IsDefined(typeof(Size), size))
{
    // safe to use size
}

O .NET Core 2.0 em diante adiciona um Enum.Parse<Size>("Large") genérico que dispensa o cast.

Listando todos os valores

Enum.GetValues retorna todos os membros, ordenados pelo valor numérico (comparado sem sinal, então os membros negativos vêm por último); Enum.GetNames retorna os nomes. É assim que você preenche uma lista suspensa ou valida contra todas as opções:

Saída:

Free       0 EUR/month
Starter    9 EUR/month
Pro       29 EUR/month
Team      99 EUR/month
Free | Starter | Pro | Team
3 paid plans

Enum.GetValues(typeof(Plan)) retorna um Array simples, daí o Cast<Plan>() antes do LINQ. No .NET 5 em diante, Enum.GetValues<Plan>() retorna diretamente um Plan[] tipado.

Flags: combinando valores

Alguns enums descrevem um conjunto de opções em vez de uma escolha: permissões de arquivo, dias em que uma loja abre, canais de notificação. Dê a cada membro o seu próprio bit (1, 2, 4, 8, ...), adicione None = 0 e marque o enum com [Flags]. Os valores então se combinam com |:

Saída:

Read, Share
Editor, Share
True
False
Editor
3
Read, Delete
True

O que cada operador faz: | liga bits, & ~X os desliga, ^ os inverte, e (value & X) != 0 ou value.HasFlag(X) os testa. HasFlag(X) significa "todos os bits de X estão ligados", então HasFlag(None) é verdadeiro para qualquer valor, e HasFlag(Editor) exige Read e Write.

Repare na segunda linha: quando uma combinação com nome cobre parte dos bits ligados, o ToString a usa, então Read | Write | Share é impresso como Editor, Share. Tenha isso em mente antes de converter a saída do ToString com qualquer coisa que não seja Enum.Parse.

O atributo não muda a aritmética. Ele muda a formatação: sem [Flags], Read | Share é impresso como 9, porque nenhum membro sozinho tem esse valor. Com ele, ToString e Parse trabalham com a forma separada por vírgulas. Os membros ainda precisam ser potências de dois; escrever Read, Write, Delete com a numeração padrão (0, 1, 2) faz Write | Delete valer 3, um valor sem significado.

switch sobre um enum

switch é a forma natural de agir sobre um enum. Inclua um ramo default, porque uma variável enum pode guardar valores sem nome:

switch (status)
{
    case OrderStatus.Pending:
    case OrderStatus.Paid:
        return "Preparing";
    case OrderStatus.Shipped:
        return "On the way";
    case OrderStatus.Delivered:
        return "Delivered";
    default:
        return "Unknown";
}

Desde o C# 8, uma expressão switch é mais curta. Sem um braço _, o compilador avisa: CS8509 quando falta um membro com nome, e CS8524 quando todos os nomes são tratados, mas valores sem nome como (OrderStatus)7 não:

string text = status switch
{
    OrderStatus.Pending or OrderStatus.Paid => "Preparing",   // 'or' pattern: C# 9
    OrderStatus.Shipped => "On the way",
    OrderStatus.Delivered => "Delivered",
    OrderStatus.Cancelled => "Cancelled",
    _ => throw new ArgumentOutOfRangeException(nameof(status)),
};

Valores padrão e indefinidos

O valor padrão de qualquer enum é 0, tenha ou não um membro esse valor. Campos, elementos de array e um TryParse que falhou produzem esse valor. Projete pensando nisso:

  • Faça do 0 um membro com significado de "não definido" (None, Unknown) em vez de uma escolha real. Senão um campo não inicializado passa a ser lido, sem aviso, como a primeira opção real.
  • Valide números vindos de fora com Enum.IsDefined. Em enums [Flags], IsDefined retorna false para combinações sem nome (Read | Share), então verifique os bits: (value & ~Permissions.All) == 0, com um membro All que cobre todos os bits.

Erros comuns

  • Confiar só no TryParse. Strings numéricas são convertidas, e uma conversão que falha dá 0. Acrescente Enum.IsDefined.
  • Depender da numeração implícita para valores guardados. Inserir um membro renumera os seguintes. Atribua valores explícitos a qualquer enum que seja persistido.
  • Flags sem potências de dois. A numeração padrão (0, 1, 2, 3) sobrepõe bits. Use 1, 2, 4, 8, ou 1 << n.
  • Mostrar o ToString() aos usuários. Nomes de membros são identificadores de código. Mapeie os valores para textos de exibição.
  • Nenhum default em um switch. Um enum pode guardar valores fora dos membros com nome.

Perguntas frequentes

Como converter um enum para string em C#?

Chame ToString(): OrderStatus.Shipped.ToString() retorna "Shipped", e a interpolação de strings faz o mesmo. ToString("D") dá o número no lugar. Para um nome conhecido em tempo de compilação, nameof(OrderStatus.Shipped) é uma constante. Para textos mostrados ao usuário, com espaços ou traduções, mapeie os valores para strings você mesmo (um switch ou um dicionário) em vez de depender do nome do membro.

Como converter uma string para enum em C#?

Use Enum.TryParse<OrderStatus>(text, true, out var status), que retorna false em vez de lançar exceção quando o texto não corresponde a nenhum membro (o true faz ignorar maiúsculas e minúsculas). Enum.Parse(typeof(OrderStatus), text) lança ArgumentException em entradas inválidas. Os dois também aceitam strings numéricas como "42", então verifique o resultado com Enum.IsDefined quando a entrada vier dos usuários.

Como converter entre um enum e um int em C#?

Faça o cast em qualquer direção: int code = (int)OrderStatus.Paid; e var status = (OrderStatus)2;. O cast a partir de int nunca falha, nem para números sem membro correspondente; o resultado é um valor do enum que é impresso como o número. Valide com Enum.IsDefined(typeof(OrderStatus), value) quando o número vier de fora.

Como percorrer todos os valores de um enum em C#?

foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus))) visita todos os membros na ordem dos valores numéricos. Desde o .NET 5 existe uma versão genérica, Enum.GetValues<OrderStatus>(), que dispensa o cast. Enum.GetNames(typeof(OrderStatus)) retorna os nomes como strings.

O que o [Flags] faz em um enum em C#?

Ele marca um enum cujos valores são bits feitos para serem combinados com |, como Read | Write. Dê a cada membro uma potência de dois (1, 2, 4, 8) e um None = 0. O atributo faz o ToString() imprimir as combinações como "Read, Write" e permite que Enum.Parse leia esse formato de volta. Teste um bit com HasFlag ou (value & Permissions.Write) != 0.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR