Back to Blog
C#

C# Int to Enum: Casting and Validation

c# int to enum: Learn how to convert an int to an enum in C# safely, including casting, Enum.TryParse, and validation with Enum.IsDefined to avoid invalid values.

C# enumsenum conversionEnum.TryParseEnum.IsDefinedtype safety
Illustration of an integer value being converted to an enum type in C# with a validation shield.

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

Converting an int to an enum in C# is a common operation when reading numeric values from a database, configuration file, or API. The direct cast is the simplest approach, but it has a critical flaw: it accepts any integer, even those not defined in the enum. This article explains the cast, the validation methods, and when to use each.

The Direct Cast and Its Risks

The most straightforward way to convert an int to an enum is to use a cast:

public enum Status { Pending = 1, Active = 2, Suspended = 3 } int input = 2; Status status = (Status)input;

This works because every enum is backed by an integral type, by default int. The cast simply reinterprets the integer bits as the enum type. However, the cast does not check whether the value is actually defined in the enum. If input is 5, the cast still produces a Status value with that numeric value, even though no named member corresponds to it. This can lead to logic errors, unexpected behavior in switch statements, or invalid values being persisted.

For example, consider a method that returns a friendly name for a status:

public static string GetStatusName(Status status) { switch (status) { case Status.Pending: return "Pending"; case Status.Active: return "Active"; case Status.Suspended: return "Suspended"; default: return "Unknown"; } }

If an undefined value like (Status)5 is passed, the method returns "Unknown" without indicating that the input was invalid. In many cases, you want to detect that condition explicitly rather than silently accepting an invalid state.

Using Enum.IsDefined to Validate

To ensure that an integer corresponds to a defined enum member, use Enum.IsDefined. This method checks whether a value exists in the enum's definition.

int input = 5; if (Enum.IsDefined(typeof(Status), input)) { Status status = (Status)input; // Use the valid status } else { // Handle the invalid input }

Enum.IsDefined works with the underlying integral value, so you can pass the int directly. It also works with the string name, but for numeric conversion the integer overload is what you need. Note that Enum.IsDefined performs a linear search through the enum's values, so it is slightly slower than a direct cast. For most applications, the cost is negligible, but in a tight loop processing millions of values, you might want to avoid it or use a cached HashSet of valid values.

Another subtlety: Enum.IsDefined does not handle composite enum values (those with the [Flags] attribute) correctly if you expect combinations of flags to be valid. For a flags enum, any combination of defined bits is valid, but Enum.IsDefined only returns true for the exact named combinations. In that case, you need a different validation strategy, such as checking that all bits are within the combined mask.

Converting with Enum.TryParse

Enum.TryParse is a common alternative, but it is designed for string parsing, not direct integer conversion. When you pass an integer as the value to parse, Enum.TryParse treats it as a string representation of the numeric value or the name. For example:

int input = 2; if (Enum.TryParse<Status>(input.ToString(), out Status status)) { // status is Status.Active }

This works because Enum.TryParse will parse a numeric string and convert it to the enum value. However, it does not validate that the value is defined. By default, Enum.TryParse accepts any numeric string, even if the resulting enum value is not defined. To require a defined value, pass ignoreCase: false (or true for names) and use the Enum.IsDefined check afterward. The overload with ignoreCase only affects name parsing, not numeric parsing.

A more efficient approach is to combine Enum.TryParse with Enum.IsDefined when you need to handle both numeric and string input in a uniform way:

public static bool TryConvertToStatus(int input, out Status status) { if (Enum.IsDefined(typeof(Status), input)) { status = (Status)input; return true; } status = default; return false; }

This avoids the string allocation and is clearer about the intent.

Handling Out-of-Range Values

When an integer does not map to any enum member, you have several options. The safest is to reject the value and treat it as an error, especially when the data comes from an external source. In a REST API, you might return a validation error. In a database read, you might log the issue and use a default value.

If you decide to use a default, be explicit about it:

Status status = Enum.IsDefined(typeof(Status), input) ? (Status)input : Status.Pending;

But be careful: relying on a default can hide data corruption. A better practice is to fail fast and surface the problem.

For flags enums, the validation logic is different. A flags enum like FileAccess with values Read = 1, Write = 2 allows combinations like Read | Write = 3. Enum.IsDefined would return false for 3 because it is not a named constant. To validate a flags enum, you need to check that all bits are within the union of defined values:

[Flags] public enum FileAccess { Read = 1, Write = 2 } int input = 3; int definedMask = 0; foreach (FileAccess value in Enum.GetValues<FileAccess>()) { definedMask |= (int)value; } bool isValid = (input & ~definedMask) == 0;

This ensures that no undefined bits are set.

Performance and Allocation Considerations

The direct cast is essentially free—it is a compile-time operation that generates no runtime method calls. Enum.IsDefined uses reflection under the hood and performs a linear search, so it is slower. Enum.TryParse with a string conversion allocates a string and then parses it, which is the most expensive option.

For high-throughput scenarios, you can cache the set of valid enum values in a HashSet<int> to make validation O(1):

private static readonly HashSet<int> ValidStatusValues = new( Enum.GetValues<Status>().Select(s => (int)s)); public static bool IsValidStatus(int input) => ValidStatusValues.Contains(input);

This avoids repeated reflection calls and is significantly faster when validating many values. The tradeoff is the upfront cost of building the set and the memory it occupies. For a small enum, the overhead is trivial.

Another consideration is that Enum.IsDefined is not generic in the type parameter; it requires a Type object and returns a bool. This can lead to boxing if you pass a value type. The HashSet approach avoids that by working directly with int.

Choosing the Right Approach

The decision depends on the context and the source of the integer.

  • Use a direct cast when you are certain the value is defined, such as when you control the input and have already validated it elsewhere. This is the fastest and most readable option.
  • Use Enum.IsDefined plus a cast when the integer comes from an untrusted source and you need to validate it before conversion. This is the standard pattern for API input or database reads.
  • Use Enum.TryParse when you are also handling string input and want a unified parsing path. But remember that it does not validate numeric values by default, so combine it with Enum.IsDefined if needed.
  • Use a cached HashSet<int> when you are processing a large volume of values and need high-throughput validation. This is an optimization that pays off in loops or batch processing.

For flags enums, avoid Enum.IsDefined and implement a bitwise validation that checks for undefined bits. This is a common source of bugs because developers assume Enum.IsDefined works for combinations.

Finally, consider the maintainability of your enum. If you add a new member, any validation logic that relies on a hard-coded list of valid values will need to be updated. Using Enum.GetValues to build the set dynamically avoids this maintenance burden. The same applies to Enum.IsDefined, which automatically reflects the current definition.

By understanding the behavior of each conversion method and its validation semantics, you can choose the right tool for your specific scenario and avoid the subtle bugs that come from invalid enum values.