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
0um 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],IsDefinedretornafalsepara combinações sem nome (Read | Share), então verifique os bits:(value & ~Permissions.All) == 0, com um membroAllque cobre todos os bits.
Erros comuns
- Confiar só no
TryParse. Strings numéricas são convertidas, e uma conversão que falha dá0. AcrescenteEnum.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
defaultem 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.