Menu

C# Enum: Werte, ToString, Parse, Flags und Durchlaufen

Wie Enums in C# funktionieren: benannte Konstanten deklarieren, zugrunde liegende Ganzzahlwerte und Casts, ein Enum in einen String und einen String mit Parse und TryParse in ein Enum umwandeln, alle Werte auflisten, [Flags] mit bitweisen Operatoren und HasFlag, switch über ein Enum und der Umgang mit undefinierten Werten.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Ein Enum (Aufzählung) ist ein Typ, dessen Werte eine feste Menge benannter Konstanten sind: Bestellstatus, Wochentage, Log-Level. Darunter ist jeder Name eine Ganzzahl, aber das Typsystem verhindert, dass ein OrderStatus mit einem einfachen int oder einem anderen Enum verwechselt wird.

Ein Enum deklarieren und verwenden

Liste die Member-Namen in geschweiften Klammern auf. Standardmäßig ist der erste 0 und jeder weitere um eins höher:

Ausgabe:

Paid
On its way
True
2

Das Enum ist ein echter Typ: Eine Methode, die ein OrderStatus nimmt, lässt sich nicht versehentlich mit 3 oder mit einem LogLevel aufrufen. Enums sind Werttypen, sie sind also nie null und werden mit == nach Wert verglichen.

Explizite Werte und der zugrunde liegende Typ

Du kannst die Zahlen selbst vergeben. Das zählt, wann immer die Zahl dein Programm verlässt (eine Datenbankspalte, ein HTTP-Status, ein Dateiformat), denn dann zerstört eine Neunummerierung gespeicherte Daten:

Ausgabe:

404
Created
418
1
Byte

Zwei Dinge fallen auf. Ein Cast von int in ein Enum scheitert nie: (HttpStatus)418 ist ein gültiger Wert, der nur keinen Namen hat, und er wird als Zahl ausgegeben. Und der zugrunde liegende Typ kann jeder Ganzzahltyp sein (byte, short, long, ...), was nur für speicherkritischen Code zählt; int ist der Standard und fast immer die richtige Wahl.

Wenn du später Member hinzufügst, hänge sie ans Ende oder gib explizite Werte an. Refunded zwischen Paid und Shipped einzufügen ändert stillschweigend die Zahl jedes nachfolgenden Members.

Enum in String

ToString() gibt den Member-Namen zurück, und den verwenden auch Console.WriteLine und die String-Interpolation. Formatstrings ändern die Ausgabe:

Ausgabe:

Warning
2
00000002
[Warning]
Error
Error
Needs attention

Member-Namen sind Bezeichner, sie können also keine Leerzeichen enthalten und werden nicht übersetzt. Für Text, der Benutzern angezeigt wird, bilde die Werte selbst ab, wie es Label tut, oder mit einem Dictionary<LogLevel, string>. Manche Codebasen setzen an jeden Member ein Attribut [Description("Needs attention")] und lesen es per Reflection; die Seite zu Reflection und Attributen zeigt, wie dieses Nachschlagen funktioniert.

String in Enum: Parse und TryParse

Enum.Parse wandelt einen Namen zurück in einen Wert und wirft ArgumentException, wenn nichts passt. Enum.TryParse gibt stattdessen false zurück, und das willst du bei jeder Eingabe, die du nicht kontrollierst:

Ausgabe:

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

Die letzten beiden Zeilen sind die Falle. Beide Methoden akzeptieren numerische Strings, "7" wird also erfolgreich zu einem Size geparst, das keinen Namen hat. Und ein gescheitertes TryParse setzt das Ergebnis auf 0, was hier das gültig aussehende Small ist. Wenn der Text aus einem Query-String, einer Konfigurationsdatei oder einem Formular kommt, prüfe immer sowohl den Rückgabewert als auch Enum.IsDefined:

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

.NET Core 2.0 und neuer bieten ein generisches Enum.Parse<Size>("Large"), das keinen Cast braucht.

Alle Werte auflisten

Enum.GetValues gibt jeden Member zurück, sortiert nach Zahlenwert (vorzeichenlos verglichen, negative Member kommen also zuletzt); Enum.GetNames gibt ihre Namen zurück. So füllst du eine Dropdown-Liste oder validierst gegen jede Option:

Ausgabe:

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)) gibt ein einfaches Array zurück, daher das Cast<Plan>() vor LINQ. Ab .NET 5 gibt Enum.GetValues<Plan>() direkt ein typisiertes Plan[] zurück.

Flags: Werte kombinieren

Manche Enums beschreiben eine Menge von Optionen statt einer Auswahl: Dateiberechtigungen, Öffnungstage eines Ladens, Benachrichtigungskanäle. Gib jedem Member sein eigenes Bit (1, 2, 4, 8, ...), füge None = 0 hinzu und markiere das Enum mit [Flags]. Werte lassen sich dann mit | kombinieren:

Ausgabe:

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

Was jeder Operator tut: | setzt Bits, & ~X löscht sie, ^ schaltet sie um, und (value & X) != 0 oder value.HasFlag(X) prüft sie. HasFlag(X) bedeutet „alle Bits von X sind gesetzt“, also ist HasFlag(None) für jeden Wert wahr, und HasFlag(Editor) verlangt sowohl Read als auch Write.

Beachte die zweite Zeile: Wenn eine benannte Kombination einige der gesetzten Bits abdeckt, verwendet ToString sie, Read | Write | Share wird also als Editor, Share ausgegeben. Behalte das im Kopf, bevor du die Ausgabe von ToString mit etwas anderem als Enum.Parse parst.

Das Attribut ändert nichts an der Arithmetik. Es ändert die Formatierung: Ohne [Flags] wird Read | Share als 9 ausgegeben, weil kein einzelner Member diesen Wert hat. Mit dem Attribut arbeiten ToString und Parse beide mit der kommagetrennten Form. Die Member müssen trotzdem Zweierpotenzen sein; Read, Write, Delete mit der Standardnummerierung (0, 1, 2) zu schreiben macht Write | Delete gleich 3, einen bedeutungslosen Wert.

switch über ein Enum

switch ist der natürliche Weg, auf ein Enum zu reagieren. Nimm einen default-Zweig mit auf, weil eine Enum-Variable Werte ohne Namen enthalten kann:

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";
}

Seit C# 8 ist ein switch-Ausdruck kürzer. Ohne _-Arm warnt der Compiler: CS8509, wenn ein benannter Member fehlt, und CS8524, wenn jeder Name behandelt ist, unbenannte Werte wie (OrderStatus)7 aber nicht:

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)),
};

Standardwerte und undefinierte Werte

Der Standardwert jedes Enums ist 0, egal ob ein Member diesen Wert hat. Felder, Array-Elemente und ein gescheitertes TryParse erzeugen ihn alle. Plane dafür:

  • Mach 0 zu einem sinnvollen Member für „nicht gesetzt“ (None, Unknown) statt zu einer echten Auswahl. Sonst liest sich ein nicht initialisiertes Feld stillschweigend wie die erste echte Option.
  • Validiere Zahlen von außen mit Enum.IsDefined. Bei [Flags]-Enums gibt IsDefined für Kombinationen ohne Namen false zurück (Read | Share), prüfe also stattdessen die Bits: (value & ~Permissions.All) == 0, mit einem Member All, der jedes Bit abdeckt.

Häufige Fehler

  • Nur auf TryParse vertrauen. Numerische Strings werden geparst, und ein gescheitertes Parsen liefert 0. Ergänze Enum.IsDefined.
  • Sich bei gespeicherten Werten auf implizite Nummerierung verlassen. Einen Member einzufügen nummeriert die nachfolgenden neu. Gib jedem Enum, das gespeichert wird, explizite Werte.
  • Flags ohne Zweierpotenzen. Die Standardnummerierung (0, 1, 2, 3) überschneidet Bits. Nimm 1, 2, 4, 8 oder 1 << n.
  • Benutzern ToString() zeigen. Member-Namen sind Code-Bezeichner. Bilde Werte auf Anzeigetext ab.
  • Kein default in einem switch. Ein Enum kann Werte außerhalb seiner benannten Member enthalten.

Häufig gestellte Fragen

Wie wandle ich in C# ein Enum in einen String um?

Rufe ToString() auf: OrderStatus.Shipped.ToString() gibt "Shipped" zurück, und die String-Interpolation tut dasselbe. ToString("D") liefert stattdessen die Zahl. Für einen zur Kompilierzeit bekannten Namen ist nameof(OrderStatus.Shipped) eine Konstante. Für Text für Benutzer mit Leerzeichen oder Übersetzungen bilde Werte selbst auf Strings ab (ein switch oder ein Dictionary), statt dich auf den Member-Namen zu verlassen.

Wie wandle ich in C# einen String in ein Enum um?

Nimm Enum.TryParse<OrderStatus>(text, true, out var status), das false zurückgibt, statt zu werfen, wenn der Text auf keinen Member passt (das true ignoriert Groß- und Kleinschreibung). Enum.Parse(typeof(OrderStatus), text) wirft bei ungültiger Eingabe ArgumentException. Beide akzeptieren auch numerische Strings wie "42", prüfe das Ergebnis also mit Enum.IsDefined, wenn die Eingabe von Benutzern kommt.

Wie wandle ich in C# zwischen einem Enum und einem int um?

Caste in beide Richtungen: int code = (int)OrderStatus.Paid; und var status = (OrderStatus)2;. Der Cast aus int scheitert nie, auch nicht bei Zahlen ohne passenden Member; das Ergebnis ist ein Enum-Wert, der als Zahl ausgegeben wird. Validiere mit Enum.IsDefined(typeof(OrderStatus), value), wenn die Zahl von außen kommt.

Wie durchlaufe ich in C# alle Werte eines Enums?

foreach (OrderStatus s in Enum.GetValues(typeof(OrderStatus))) besucht jeden Member in der Reihenfolge seiner Zahlenwerte. Seit .NET 5 gibt es eine generische Version, Enum.GetValues<OrderStatus>(), die keinen Cast braucht. Enum.GetNames(typeof(OrderStatus)) gibt die Namen als Strings zurück.

Was macht [Flags] an einem C#-Enum?

Es kennzeichnet ein Enum, dessen Werte Bits sind, die mit | kombiniert werden sollen, etwa Read | Write. Gib jedem Member eine Zweierpotenz (1, 2, 4, 8) und ein None = 0. Das Attribut sorgt dafür, dass ToString() Kombinationen als "Read, Write" ausgibt und Enum.Parse dieses Format zurücklesen kann. Prüfe ein Bit mit HasFlag oder (value & Permissions.Write) != 0.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S