Un enum (enumerazione) è un tipo i cui valori sono un insieme fisso di costanti con nome: stati di un ordine, giorni della settimana, livelli di log. Sotto sotto ogni nome è un intero, ma il sistema dei tipi impedisce che un OrderStatus venga confuso con un semplice int o con un altro enum.
Dichiarare e usare un enum
Elenca i nomi dei membri tra parentesi graffe. Per impostazione predefinita il primo vale 0 e ognuno dei successivi vale uno in più:
Output:
Paid
On its way
True
2
L'enum è un tipo vero e proprio: un metodo che riceve un OrderStatus non può essere chiamato per errore con 3 o con un LogLevel. Gli enum sono tipi valore, quindi non sono mai null e si confrontano con == per valore.
Valori espliciti e tipo sottostante
Puoi assegnare tu i numeri. Questo conta ogni volta che il numero esce dal tuo programma (una colonna di database, uno stato HTTP, un formato di file), perché a quel punto rinumerare rompe i dati memorizzati:
Output:
404
Created
418
1
Byte
Due cose da notare. Il cast di un int a un enum non fallisce mai: (HttpStatus)418 è un valore valido che semplicemente non ha un nome, e viene stampato come numero. E il tipo sottostante può essere qualsiasi tipo intero (byte, short, long, ...), cosa che conta solo nel codice attento allo spazio occupato; int è il valore predefinito e quasi sempre la scelta giusta.
Quando aggiungi membri in seguito, aggiungili in fondo o assegna valori espliciti. Inserire Refunded tra Paid e Shipped cambia silenziosamente il numero di ogni membro successivo.
Da enum a stringa
ToString() restituisce il nome del membro, che è anche ciò che usano Console.WriteLine e l'interpolazione di stringhe. Le stringhe di formato cambiano l'output:
Output:
Warning
2
00000002
[Warning]
Error
Error
Needs attention
I nomi dei membri sono identificatori, quindi non possono contenere spazi e non vengono tradotti. Per il testo mostrato agli utenti, associa tu i valori, come fa Label, oppure con un Dictionary<LogLevel, string>. Alcuni progetti mettono un attributo [Description("Needs attention")] su ogni membro e lo leggono con la reflection; la pagina su reflection e attributi mostra come funziona quella ricerca.
Da stringa a enum: Parse e TryParse
Enum.Parse riconverte un nome in un valore e lancia ArgumentException se nulla corrisponde. Enum.TryParse restituisce invece false, ed è ciò che vuoi per qualsiasi input che non controlli:
Output:
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
Le ultime due righe sono la trappola. Entrambi i metodi accettano stringhe numeriche, quindi "7" viene convertito senza errori in un Size che non ha nome. E un TryParse fallito imposta il risultato a 0, che qui è Small, un valore dall'aspetto valido. Quando il testo arriva da una query string, da un file di configurazione o da un form, controlla sempre sia il valore di ritorno sia Enum.IsDefined:
if (Enum.TryParse(input, true, out Size size) && Enum.IsDefined(typeof(Size), size))
{
// safe to use size
}
.NET Core 2.0 e successivi aggiungono un Enum.Parse<Size>("Large") generico che non richiede cast.
Elencare tutti i valori
Enum.GetValues restituisce ogni membro, ordinato per valore numerico (confrontato come valore senza segno, quindi i membri negativi finiscono in fondo); Enum.GetNames restituisce i loro nomi. È così che riempi un menu a tendina o fai una validazione su tutte le opzioni:
Output:
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)) restituisce un semplice Array, da qui il Cast<Plan>() prima di LINQ. Su .NET 5 e successivi, Enum.GetValues<Plan>() restituisce direttamente un Plan[] tipizzato.
Flags: combinare valori
Alcuni enum descrivono un insieme di opzioni invece di una sola scelta: permessi sui file, giorni di apertura di un negozio, canali di notifica. Dai a ogni membro un bit proprio (1, 2, 4, 8, ...), aggiungi None = 0 e marca l'enum con [Flags]. I valori si combinano allora con |:
Output:
Read, Share
Editor, Share
True
False
Editor
3
Read, Delete
True
Cosa fa ciascun operatore: | imposta i bit, & ~X li azzera, ^ li inverte, e (value & X) != 0 o value.HasFlag(X) li verificano. HasFlag(X) significa "tutti i bit di X sono impostati", quindi HasFlag(None) è vero per ogni valore, e HasFlag(Editor) richiede sia Read sia Write.
Nota la seconda riga: quando una combinazione con nome copre alcuni dei bit impostati, ToString la usa, quindi Read | Write | Share viene stampato come Editor, Share. Tienilo presente prima di analizzare l'output di ToString con qualcosa di diverso da Enum.Parse.
L'attributo non cambia l'aritmetica. Cambia la formattazione: senza [Flags], Read | Share viene stampato come 9, perché nessun singolo membro ha quel valore. Con l'attributo, ToString e Parse lavorano entrambi con la forma separata da virgole. I membri devono comunque essere potenze di due; scrivere Read, Write, Delete con la numerazione predefinita (0, 1, 2) fa sì che Write | Delete valga 3, un valore senza senso.
Switch su un enum
switch è il modo naturale di agire in base a un enum. Includi un ramo default, perché una variabile enum può contenere valori senza 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";
}
Da C# 8 una switch expression è più breve. Senza un ramo _ il compilatore dà un warning: CS8509 quando manca un membro con nome, e CS8524 quando tutti i nomi sono gestiti ma i valori senza nome come (OrderStatus)7 no:
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)),
};
Valori predefiniti e non definiti
Il valore predefinito di qualsiasi enum è 0, che esista o no un membro con quel valore. Campi, elementi di array e un TryParse fallito lo producono tutti. Progetta tenendone conto:
- Rendi
0un membro significativo per "non impostato" (None,Unknown) invece di una scelta reale. Altrimenti un campo non inizializzato viene letto silenziosamente come la prima opzione reale. - Valida i numeri che arrivano dall'esterno con
Enum.IsDefined. Per gli enum[Flags],IsDefinedrestituiscefalseper le combinazioni senza nome (Read | Share), quindi controlla invece i bit:(value & ~Permissions.All) == 0con un membroAllche copre ogni bit.
Errori comuni
- Fidarsi solo di
TryParse. Le stringhe numeriche vengono convertite, e una conversione fallita dà0. AggiungiEnum.IsDefined. - Affidarsi alla numerazione implicita per valori memorizzati. Inserire un membro rinumera quelli successivi. Assegna valori espliciti a ogni enum che viene salvato.
- Flags senza potenze di due. La numerazione predefinita (0, 1, 2, 3) sovrappone i bit. Usa 1, 2, 4, 8 oppure
1 << n. - Mostrare
ToString()agli utenti. I nomi dei membri sono identificatori del codice. Associa i valori a un testo da visualizzare. - Nessun
defaultin uno switch. Un enum può contenere valori al di fuori dei suoi membri con nome.
Domande frequenti
Come converto un enum in stringa in C#?
Chiama ToString(): OrderStatus.Shipped.ToString() restituisce "Shipped", e l'interpolazione di stringhe fa lo stesso. ToString("D") restituisce invece il numero. Per un nome noto in fase di compilazione, nameof(OrderStatus.Shipped) è una costante. Per il testo destinato agli utenti, con spazi o traduzioni, associa tu i valori alle stringhe (con uno switch o un dizionario) invece di affidarti al nome del membro.
Come converto una stringa in enum in C#?
Usa Enum.TryParse<OrderStatus>(text, true, out var status), che restituisce false invece di lanciare un'eccezione quando il testo non corrisponde ad alcun membro (il true ignora maiuscole e minuscole). Enum.Parse(typeof(OrderStatus), text) lancia ArgumentException con un input non valido. Entrambi accettano anche stringhe numeriche come "42", quindi verifica il risultato con Enum.IsDefined quando l'input arriva dagli utenti.
Come converto tra enum e int in C#?
Fai un cast in entrambe le direzioni: int code = (int)OrderStatus.Paid; e var status = (OrderStatus)2;. Il cast da int non fallisce mai, nemmeno per numeri senza un membro corrispondente; il risultato è un valore enum che viene stampato come numero. Valida con Enum.IsDefined(typeof(OrderStatus), value) quando il numero arriva dall'esterno.
Come scorro tutti i valori di un enum in C#?
foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus))) visita ogni membro in ordine di valore numerico. Da .NET 5 esiste una versione generica, Enum.GetValues<OrderStatus>(), che non richiede cast. Enum.GetNames(typeof(OrderStatus)) restituisce i nomi come stringhe.
Cosa fa [Flags] su un enum C#?
Marca un enum i cui valori sono bit da combinare con |, come Read | Write. Dai a ogni membro una potenza di due (1, 2, 4, 8) e un None = 0. L'attributo fa sì che ToString() stampi le combinazioni come "Read, Write" e permette a Enum.Parse di rileggere quel formato. Verifica un bit con HasFlag o (value & Permissions.Write) != 0.