How to Work with Enum Underlying Types in C
By default, every enumeration in C# uses the 32-bit signed integer
(int) as its underlying storage mechanism. However, C#
allows developers to override this default and choose from a specific
set of integral types—such as byte, short, or
long—to optimize memory allocation, maintain strict API
contracts, or integrate cleanly with databases and network protocols.
This guide covers how to declare an enum with a custom underlying type,
safely cast between the enum and its primitive value, and retrieve
underlying type information at runtime.
Supported Underlying Types
An enum in C# can inherit from any integral type except
char. The allowed types are:
byteandsbyte(8-bit)shortandushort(16-bit)intanduint(32-bit)longandulong(64-bit)
Declaring an Enum with a Specific Underlying Type
To specify an underlying type, place a colon (:)
followed by the chosen type name after the enum identifier:
public enum NetworkState : byte
{
Disconnected = 0,
Connecting = 1,
Connected = 2,
Error = 255
}In this example, each member of NetworkState requires
only 1 byte of memory instead of the default 4 bytes. If an assigned
value exceeds the range of the underlying type, the C# compiler produces
a compile-time error.
Casting Between Enums and Underlying Types
C# requires explicit casting to convert an enum instance to or from its underlying type:
// Enum to underlying type
NetworkState state = NetworkState.Connected;
byte rawValue = (byte)state; // rawValue is 2
// Underlying type to enum
byte input = 1;
NetworkState restoredState = (NetworkState)input; // restoredState is NetworkState.ConnectingWhile casting to an integral type of the same size or larger is safe, casting directly to an incompatible integral type can cause truncation or runtime errors if done improperly. Always match the explicit cast to the defined underlying type or a larger type.
Inspecting Underlying Types at Runtime
To determine the underlying storage type dynamically, use the
Enum.GetUnderlyingType method:
Type underlyingType = Enum.GetUnderlyingType(typeof(NetworkState));
Console.WriteLine(underlyingType); // Outputs: System.ByteThis is particularly useful when writing generic serialization logic, database mapping layers, or reflection-based validators where the enum structure must be inspected programmatically.
Using
Underlying Types with the [Flags] Attribute
When using the [Flags] attribute for bitwise operations,
choosing a larger underlying type like long expands the
maximum number of distinct bit flags from 32 to 64:
[Flags]
public enum UserPermissions : ulong
{
None = 0,
Read = 1UL << 0,
Write = 1UL << 1,
Execute = 1UL << 2,
// Can safely scale up to 1UL << 63
}Choosing the correct underlying type ensures efficient memory usage in high-throughput applications, prevents overflow errors in bitmask scenarios, and enforces correct data representations across system boundaries.