BrandGhost
C# Enum to String: Conversion Patterns and Best Practices

C# Enum to String: Conversion Patterns and Best Practices

C# Enum to String: Conversion Patterns and Best Practices

Enums are great for type safety and readability -- until you need to log them, display them in a UI, serialize them to JSON, or send them across an API. At that point you need C# enum to string conversion, and there are more options than most developers realize.

This guide covers every conversion pattern you are likely to need: ToString(), Enum.GetName, format specifiers, JSON serialization, custom display names, and safe parsing in both directions.

The Basics: ToString()

The simplest C# enum to string conversion is .ToString():

public enum OrderStatus
{
    Pending,
    Processing,
    Shipped,
    Delivered,
    Cancelled
}

OrderStatus status = OrderStatus.Shipped;

string name = status.ToString();  // "Shipped"
Console.WriteLine(name);          // Shipped

.ToString() returns the member name for defined values. For undefined values (integer casts outside the defined range), it returns the integer as a string:

OrderStatus invalid = (OrderStatus)99;
Console.WriteLine(invalid.ToString());  // "99" -- not a name!

This is a common source of bugs when data comes from external systems. Always validate before using.

Enum.GetName and Enum.GetNames

Enum.GetName<T>(value) is a .NET 5+ alternative that returns null for undefined values instead of the integer string:

string? name = Enum.GetName(OrderStatus.Shipped);  // "Shipped"
string? bad  = Enum.GetName((OrderStatus)99);       // null

// With null check
string label = Enum.GetName(status) ?? "Unknown";

This is preferable when you need to distinguish "defined member" from "undefined value." ToString() conflates the two.

Enum.GetNames<T>() returns all defined member names as a string array:

string[] names = Enum.GetNames<OrderStatus>();
// { "Pending", "Processing", "Shipped", "Delivered", "Cancelled" }

ToString Format Specifiers

ToString() accepts a format string that controls the output:

OrderStatus status = OrderStatus.Shipped;

status.ToString("G")  // "Shipped"   -- general name (default)
status.ToString("D")  // "2"         -- decimal integer
status.ToString("X")  // "00000002"  // 8-digit hex
status.ToString("F")  // "Shipped"   -- flags format (same as G for non-flags enums)

For [Flags] enums, "G" and "F" show comma-separated names for combined values:

[Flags]
public enum FileAccess
{
    None    = 0,
    Read    = 1,
    Write   = 2,
    Execute = 4
}

FileAccess access = FileAccess.Read | FileAccess.Execute;

access.ToString("G")  // "Read, Execute"
access.ToString("D")  // "5"
access.ToString("X")  // "00000005"

Use "D" when you need the integer representation for storage or wire protocols. Use "G" (the default) for logging and display.

Parsing Strings Back to Enum

Going the other direction -- string to C# enum -- has a safe and an unsafe path:

// Unsafe: throws ArgumentException for invalid names
OrderStatus parsed = Enum.Parse<OrderStatus>("Shipped");    // OK
OrderStatus bad    = Enum.Parse<OrderStatus>("Exploded");   // throws!

// Safe: returns false instead of throwing
if (Enum.TryParse("Cancelled", out OrderStatus result))
{
    Console.WriteLine(result);  // Cancelled
}
else
{
    Console.WriteLine("Invalid status name");
}

// Case-insensitive parse
bool ok = Enum.TryParse("SHIPPED", ignoreCase: true, out OrderStatus ci);
Console.WriteLine(ci);  // Shipped

Always use TryParse when input comes from outside your process (API parameters, user input, configuration files). Enum.Parse is appropriate only when you have already validated the string.

Enum.TryParse also accepts integer strings:

Enum.TryParse("2", out OrderStatus fromInt);  // Shipped

This behavior can surprise you when validating API inputs -- "2" is technically valid. To enforce name-only parsing:

public static bool TryParseByName<TEnum>(string input, out TEnum result)
    where TEnum : struct, Enum
{
    if (Enum.TryParse(input, ignoreCase: true, out result))
    {
        // Reject if input was a numeric string
        return !char.IsDigit(input.TrimStart()[0]);
    }
    return false;
}

Enum to String in JSON Serialization

System.Text.Json serializes enums as integers by default:

using System.Text.Json;

var order = new { Status = OrderStatus.Shipped };
string json = JsonSerializer.Serialize(order);
Console.WriteLine(json);  // {"Status":2}

That 2 is opaque to API consumers. To serialize as names, add JsonStringEnumConverter:

var options = new JsonSerializerOptions
{
    Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() }
};

string json = JsonSerializer.Serialize(order, options);
Console.WriteLine(json);  // {"Status":"Shipped"}

In an ASP.NET Core application, configure it globally:

// Program.cs
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(
            new System.Text.Json.Serialization.JsonStringEnumConverter());
    });

Or use the attribute per property for finer control:

using System.Text.Json.Serialization;

public class Order
{
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public OrderStatus Status { get; set; }
}

Newtonsoft.Json

If your project uses Newtonsoft.Json:

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

var settings = new JsonSerializerSettings
{
    Converters = { new StringEnumConverter() }
};

string json = JsonConvert.SerializeObject(order, settings);
Console.WriteLine(json);  // {"Status":"Shipped"}

Custom Display Names for Enum Members

Sometimes the member name is not the right display string. [Display] from System.ComponentModel.DataAnnotations is a common solution:

using System.ComponentModel.DataAnnotations;

public enum OrderStatus
{
    Pending,

    [Display(Name = "In Progress")]
    Processing,

    Shipped,

    [Display(Name = "Delivered to Customer")]
    Delivered,

    Cancelled
}

Read the display name at runtime with a helper:

using System.Reflection;

public static string GetDisplayName<TEnum>(TEnum value) where TEnum : struct, Enum
{
    var member = typeof(TEnum).GetMember(value.ToString()).FirstOrDefault();
    var attr   = member?.GetCustomAttribute<DisplayAttribute>();
    return attr?.GetName() ?? value.ToString();
}

// Usage
string label = GetDisplayName(OrderStatus.Processing);  // "In Progress"
string other = GetDisplayName(OrderStatus.Shipped);     // "Shipped" (no attribute)

An alternative is [Description] from System.ComponentModel:

using System.ComponentModel;

public enum Severity
{
    [Description("Low priority")]
    Low,

    [Description("Medium priority")]
    Medium,

    [Description("Critical -- needs immediate attention")]
    High
}

Both work fine; [Display] is commonly used in ASP.NET Core Razor pages and MVC views, while [Description] is older and requires System.ComponentModel. Pick one and use it consistently.

Enum to String in Logging

When logging enum values, .ToString() works well for structured logging frameworks:

// Serilog
_logger.Information("Order {OrderId} status changed to {Status}", orderId, status);
// Output: Order 42 status changed to Shipped

// Microsoft.Extensions.Logging
_logger.LogInformation("Order {OrderId} status changed to {Status}", orderId, status);

Both Serilog and Microsoft.Extensions.Logging call .ToString() on the status parameter. No extra conversion needed.

For structured log sinks (Elasticsearch, Seq), you may want to log both the name and the integer for filtering:

_logger.LogInformation(
    "Order {OrderId} status: {StatusName} ({StatusCode})",
    orderId,
    status.ToString(),
    (int)status);

Enum to String Performance Notes

.ToString() on an enum may involve boxing and a lookup in the enum's metadata, depending on runtime optimizations. For tight loops or high-throughput paths:

// Slow in hot paths -- boxing + metadata lookup every call
for (int i = 0; i < 10_000_000; i++)
{
    _ = status.ToString();
}

// Faster -- cache the string
string statusStr = status.ToString();
for (int i = 0; i < 10_000_000; i++)
{
    _ = statusStr;
}

// Fastest -- use a switch expression to produce the string once
string Describe(OrderStatus s) => s switch
{
    OrderStatus.Pending    => "Pending",
    OrderStatus.Processing => "Processing",
    OrderStatus.Shipped    => "Shipped",
    OrderStatus.Delivered  => "Delivered",
    OrderStatus.Cancelled  => "Cancelled",
    _                      => s.ToString()
};

For most applications, ToString() is fast enough. Optimize only when profiling shows enum string conversion as a bottleneck. For more context on working with C# enum conversions in the context of the full type system, see C# Enum: Complete Guide to Enumerations.

Parsing Enum from External Sources

A complete validation + parse helper for API endpoints:

public static class EnumParser
{
    public static TEnum ParseOrThrow<TEnum>(string input, string paramName)
        where TEnum : struct, Enum
    {
        if (string.IsNullOrWhiteSpace(input))
        {
            throw new ArgumentException($"'{paramName}' must not be empty.", paramName);
        }

        if (!Enum.TryParse<TEnum>(input, ignoreCase: true, out TEnum result))
        {
            var validValues = string.Join(", ", Enum.GetNames<TEnum>());
            throw new ArgumentException(
                $"'{input}' is not a valid {typeof(TEnum).Name}. " +
                $"Valid values: {validValues}",
                paramName);
        }

        return result;
    }
}

// Usage in an API endpoint
[HttpGet("orders")]
public IActionResult GetOrders([FromQuery] string status)
{
    OrderStatus orderStatus = EnumParser.ParseOrThrow<OrderStatus>(status, nameof(status));
    // ...
}

This provides consistent, informative error messages across all enum parameters.

For an overview of all fundamental enum patterns that set the context for conversions, see How to Use Enum in C#.

Frequently Asked Questions

How do I convert a C# enum to a string?

Call .ToString() on the enum value. For defined members, this returns the member name. Use Enum.GetName<T>(value) in .NET 5+ to get null instead of the integer string when the value is undefined.

How do I parse a string to a C# enum safely?

Use Enum.TryParse<T>(input, out T result). It returns false for invalid input instead of throwing. Use the ignoreCase: true overload for case-insensitive matching.

How do I serialize a C# enum as a string in JSON?

Add JsonStringEnumConverter to your JsonSerializerOptions. In ASP.NET Core, configure it in AddJsonOptions(). This produces "Shipped" in JSON instead of 2.

How do I add a custom display name to a C# enum member?

Apply [Display(Name = "Custom Name")] from System.ComponentModel.DataAnnotations. Read it at runtime with reflection using GetCustomAttribute<DisplayAttribute>() or a helper extension method.

Does ToString() work for [Flags] enums?

Yes. With the [Flags] attribute, ToString() returns a comma-separated list of the active flag names -- for example, "Read, Execute" instead of "5".

How do I format a C# enum as its integer value?

Use status.ToString("D") for decimal, or cast explicitly: (int)status. The "X" format produces an 8-digit hexadecimal string.

What happens when ToString() is called on an undefined enum value?

It returns the integer value as a string -- for example, "99" -- rather than a member name. Use Enum.GetName() which returns null for undefined values, making the undefined case explicit.

C# Enum Flags: Combining Values with Bitwise Operations

Learn how C# enum flags work. Understand the [Flags] attribute, bitwise OR and AND, HasFlag(), and when to use a flags enum over a regular enum in .NET.

C# Enum: Complete Guide to Enumerations in .NET

Master C# enum with this complete guide. Learn to declare enums, use flags, convert to string, apply switch pattern matching, and follow .NET best practices.

How to Use Enum in C#: Declaration, Values, and Best Practices

Learn how to use enum in C# from scratch. Covers declaring enums, assigning values, casting, comparing, iterating, and the best practices every C# developer should follow.

An error has occurred. This application may no longer respond until reloaded. Reload