Menu

Enum in C#: valori, ToString, Parse, Flags e iterazione

Come funzionano gli enum in C#: dichiarare costanti con nome, valori interi sottostanti e cast, convertire un enum in stringa e una stringa in enum con Parse e TryParse, elencare tutti i valori, [Flags] con operatori bit a bit e HasFlag, lo switch su un enum e la gestione dei valori non definiti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

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 0 un 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], IsDefined restituisce false per le combinazioni senza nome (Read | Share), quindi controlla invece i bit: (value & ~Permissions.All) == 0 con un membro All che copre ogni bit.

Errori comuni

  • Fidarsi solo di TryParse. Le stringhe numeriche vengono convertite, e una conversione fallita dà 0. Aggiungi Enum.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 default in 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.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA