Back to Blog
C#

C# Enum TryParse: Safe String-to-Enum Parsing

c# enum tryparse: Learn how to parse strings into C# enums safely with Enum.TryParse, including case handling, numeric strings, undefined values, and performance trade...

C#EnumParsingType Safety.NET
Illustration of parsing a string into a C# enum value with a validation gate

c# enum tryparse requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

Why Enum.TryParse Exists

Converting a string to an enum value is a routine operation in C# applications. Configuration files, API requests, database columns, and user input all arrive as strings, and enums are a natural way to model a fixed set of options. The naive approach is Enum.Parse, which throws an ArgumentException when the input does not match any defined member. That exception forces callers to wrap every parse in a try-catch block, which is noisy and easy to get wrong.

Enum.TryParse addresses that by returning a boolean result instead of throwing. The method was introduced in .NET 4.0 and is available in every later version of the framework and .NET. It is the standard way to handle untrusted string input when the target type is an enum.

Basic Syntax and Return Behavior

The generic form of the method is:

public static bool TryParse<TEnum>(string? value, out TEnum result) where TEnum : struct

The generic constraint restricts TEnum to value types, which includes enums but also allows structs. Passing a non-enum struct compiles but throws an ArgumentException at runtime, so the method is not fully type-safe at compile time. In practice the method is used almost exclusively with enum types.

A typical call looks like this:

public enum LogLevel { Debug, Info, Warning, Error } if (Enum.TryParse<LogLevel>(input, out var level)) { logger.Log(level, message); } else { logger.Log(LogLevel.Info, $"Unknown level: {input}"); }

The method returns true when parsing succeeds and false when it fails. When it returns false, result is set to the default value of the enum, which is zero. That default assignment matters: a failed parse leaves result at 0, which may be a valid enum member. Relying on the return value rather than inspecting result directly avoids that trap.

Case Sensitivity and the ignoreCase Parameter

By default, Enum.TryParse is case-sensitive. The string "warning" does not match the member Warning. The overload with an ignoreCase parameter changes that behavior:

Enum.TryParse<LogLevel>("warning", ignoreCase: true, out var level);

This overload is the safer choice for user-facing input, where the exact casing of enum member names should not matter. It is also the more common choice in web applications because query parameters and JSON payloads frequently arrive with inconsistent casing.

There is a subtlety: the case-insensitive comparison uses the current culture. For enum member names, which are typically ASCII identifiers, this rarely causes problems, but it is worth knowing that the behavior is culture-sensitive rather than invariant.

Numeric Strings and the Parsing Trap

Enum.TryParse does more than match member names. It also accepts numeric strings and converts them to the underlying enum value:

Enum.TryParse<LogLevel>("2", out var level); // true, level == Warning

This is a common source of bugs. A string like "2" parses successfully even though no member is named "2". Worse, any integer within the range of the underlying type parses successfully, including values that do not correspond to any defined member:

Enum.TryParse<LogLevel>("42", out var level); // true, level == (LogLevel)42

The method has no knowledge of which values are actually defined. It only checks whether the string can be converted to the underlying numeric type. If the input can be parsed as an integer, the result is true regardless of whether that integer is a valid enum member.

This behavior is documented but frequently overlooked. When the input comes from an untrusted source, accepting arbitrary numeric strings can produce enum values that no code path expects. The fix is to combine Enum.TryParse with Enum.IsDefined.

Undefined Values and Enum.IsDefined

Enum.IsDefined checks whether a value corresponds to a declared member of the enum:

if (Enum.TryParse<LogLevel>(input, ignoreCase: true, out var level) && Enum.IsDefined(typeof(LogLevel), level)) { // level is guaranteed to be a declared member }

The check is necessary because TryParse alone does not guarantee that the parsed value is meaningful. Consider a flags enum: Enum.IsDefined returns false for combined flag values, because the combination is not a single declared member. For flags enums, the validation logic must instead check that all bits are within the defined set:

[Flags] public enum Permissions { None = 0, Read = 1, Write = 2, Execute = 4 } var allDefined = (value & ~(Permissions.Read | Permissions.Write | Permissions.Execute)) == 0;

For ordinary enums, combining TryParse with IsDefined is the correct pattern for strict validation.

Performance and Allocation Behavior

Enum.TryParse relies on reflection internally. The first call for a given enum type performs type lookup and caches some metadata, but each call still involves boxing and string comparison work. For a request handler that parses one or two enum values, the cost is negligible. For a tight loop that parses thousands of values per second, the overhead becomes measurable.

When parsing is on a hot path, a hand-written mapping is often faster:

public static bool TryParseLogLevel(string input, out LogLevel level) { switch (input) { case "Debug": level = LogLevel.Debug; return true; case "Info": level = LogLevel.Info; return true; case "Warning": level = LogLevel.Warning; return true; case "Error": level = LogLevel.Error; return true; default: level = default; return false; } }

A Dictionary<string, LogLevel> with StringComparer.OrdinalIgnoreCase is a reasonable middle ground when the enum has many members. The tradeoff is maintainability: the mapping must be kept in sync with the enum definition. Enum.TryParse stays correct automatically when members are added, which is why it remains the right default for most application code.

Common Mistakes in Production Code

The most frequent mistake is ignoring the return value and reading result anyway:

Enum.TryParse<LogLevel>(input, out var level); logger.Log(level, message); // level may be LogLevel.Debug on failure

Because a failed parse assigns the default value 0, this silently logs at Debug level whenever the input is invalid. The error is invisible until someone notices that invalid inputs are being treated as valid ones.

Another mistake is using Enum.Parse without a try-catch when the input is not guaranteed to be valid. Enum.Parse throws ArgumentException for unmatched strings and OverflowException for numeric strings outside the underlying type's range. Catching both exceptions is verbose and easy to get wrong.

A third mistake is forgetting the numeric-string behavior. Code that validates input with TryParse alone accepts "99" as a valid enum value even when no member has that value. The combination with Enum.IsDefined closes that gap.

A Practical Parsing Helper

A small helper method centralizes the validation logic so it is not duplicated across call sites:

public static bool TryParseLogLevel(string input, out LogLevel level) { if (Enum.TryParse<LogLevel>(input, ignoreCase: true, out level) && Enum.IsDefined(typeof(LogLevel), level)) { return true; } level = default; return false; }

The helper accepts case-insensitive input, rejects numeric strings that do not correspond to a declared member, and always leaves level in a defined state when it returns false. Callers no longer need to remember the interaction between TryParse and IsDefined.

For enums that are parsed in many places, this helper pattern is worth keeping in a shared utility class. The alternative, repeating the two-step check at every call site, invites subtle inconsistencies when one site forgets the IsDefined check. Keeping the validation in one place also makes it easier to adjust the policy later, for example if the enum starts using flags and the validation rule changes.

c# enum tryparse: Practical Usage and Code Examples | RYUSLOG DEV