Updated on
To convert a C# enum to a string, we call ToString() on the value, or Enum.GetName<TEnum>() when the value might not match any member. Both return the member’s declared name, so Country.USA becomes "USA".
nameof(Country.USA) produces the same text at compile time, and when the string has to look different from the member name (lower case, spaces, a wire format), an attribute on the member supplies it. To go the other way, see how to convert a string or an int back to an enum.
Why Convert a C# Enum to a String?
A C# enum is a set of named constants over an integer, so unless we ask for something else, what a database or a JSON serializer receives is the number.
That number is meaningless the moment it stops being C#. A row in a database or a JSON field read by another service gets 0 where we meant USA.
Worse, the number is tied to our declaration order. Insert a new country at the top of the enum and every stored 0 means something different, with nothing in the type system to catch it.
The member name does not move when the declaration order does. Storing "USA" survives a reordering and reads correctly in a log.
The trade-off is that the name becomes part of our contract. Once a stored string says "USA", renaming the member is a breaking change, which is why a wire format usually gets its own attribute instead of borrowing the member name.
As an example of that behavior, consider we have an enum that represents one of three different countries:
public enum Country
{
USA,
India,
Australia,
}
After that, we can then create a Person class that uses this enum to set data relating to a person’s country of birth:
public class Person(string name, Country country)
{
public string Name { get; set; } = name;
public Country Country { get; set; } = country;
}
Now, if we try to save an instance of the Person class to any database with just the raw values, the Country field is represented as an int. For instance, an object that has been instantiated as new Person("Bob", Country.USA); will look like this in the database:
| Id | Name | Country |
|---|---|---|
| 1 | "Bob" | 0 |
Hence, any person looking at the data won’t be able to know in which country Bob was born. In this case, it would be better to represent Country as a string, making its meaning clearer for anyone working with that data (in the code).
Additionally, you may need to get the enum value as a string to guarantee consistency between different domains.
For instance, consider that your API must connect to another that uses its own Country enumeration. In this case, even if the countries match, a different order of declaration can scramble the information by mixing up the int values.
That risk comes from enums like our Country, which assign no explicit values. An enum declared as USA = 1, India = 2, Australia = 3 keeps its numbers whatever the declaration order, but a stored 1 still tells a reader nothing about the country.
How Do We Use Enum.GetName() in C#?
Enum.GetName looks up the name of the constant that has a given value, and returns null when no constant matches.
Since .NET 5 there are two overloads. Enum.GetName<TEnum>(TEnum value) takes the value directly and is the one to reach for. The older Enum.GetName(Type enumType, object value) takes the type and boxes the value into an object.
The generic overload is the better default for two reasons. The compiler checks the type, so passing a value from the wrong enum is a compile error rather than the wrong name at run time, and nothing is boxed on the way in.
The non-generic overload still earns its place in one case: when the enum type is only known at run time, held in a Type variable rather than written in our source.
Returning null is what makes GetName different from ToString(). A value with no matching constant gives us null to handle, instead of a string containing a number.
Microsoft’s reference documents the generic overload as public static string? GetName<TEnum>(TEnum value) where TEnum : struct, listed for .NET 5 through .NET 11.
Considering the Country enumeration that we declared earlier, we can get a string value:
var strCountry = Enum.GetName<Country>(Country.USA); Console.WriteLine(strCountry); // USA
Since the GetName() method’s return type is a nullable string, if we pass it an invalid value such as (Country)20, it returns a null value instead of a string representation.
By exploiting that behavior, we can set up a fallback logic when the value is not present:
var strCountry = Enum.GetName<Country>((Country)20); strCountry ??= "Unknown"; Console.WriteLine(strCountry); // Unknown
In this case, we used a null-coalescing assignment to set a default value of “Unknown” if there is no member with the integer value.
As such, by using the Enum.GetName() static method we can easily get the string representation of an enum.
All we need to do is to pass the enum type and the value.
How Does ToString() Convert an Enum to a String?
Calling ToString() on an enum value returns the name of the matching constant, and the number as text when no constant matches.
Its result is the default in more places than we write it. String interpolation, string.Format, Console.WriteLine and a logger’s message template all give us the same text, which is why an enum usually appears by name in a log line without anyone asking.
ToString() also takes a format specifier, and Microsoft’s reference on enumeration format strings documents four of them. "G" gives the name, "D" gives the underlying number, "X" gives that number as zero-padded hex, and "F" gives the name, falling back to a comma-separated list of matching members when the value is a combination.
There is no overload that returns text of our own choosing instead of the declared name. A lower-case value, a value with spaces or a wire format all need something else, such as an attribute, which a later section covers.
To get the string value of Bob’s country that we instantiated in the first section, we only need to call the ToString() method on it:
var bob = new Person("Bob", Country.USA);
var strCountry = bob.Country.ToString();
Console.WriteLine(strCountry); // USA
In comparison, if we wanted to do the same operation using the GetName() method:
var bob = new Person("Bob", Country.USA);
var strCountry = Enum.GetName<Country>(bob.Country);
Console.WriteLine(strCountry); // USA
The difference isn’t that huge, but calling the ToString() method directly is slightly simpler and makes our code more readable.
Even if we don’t have an instance with a set enum value, it’s still possible to use the ToString() method. Since its members are constants, calling the method from one of them directly works the same:
var strCountry = Country.India.ToString(); Console.WriteLine(strCountry); // India
The GetName() method works here too, but it is not usually the best choice: reach for it when we are starting from an integer value. In most other cases ToString() is the simpler way to get an enum member as a string in C#.
The format specifiers go into the same ToString() call. Here are all four, plus a value with no matching member, with the printed output in the comments:
var country = Country.Australia;
Console.WriteLine(country.ToString("G")); // Australia
Console.WriteLine(country.ToString("D")); // 2
Console.WriteLine(country.ToString("X")); // 00000002
Console.WriteLine(country.ToString("F")); // Australia
Console.WriteLine(((Country)20).ToString()); // 20
"X" prints eight digits because Country is backed by an int, which takes four bytes, and the format gives two hex digits per byte. "F" only looks different on a value that combines members: ((Country)3).ToString("F") prints India, Australia where ToString() prints 3. Enums meant to combine members carry the Flags attribute, and on those a plain ToString() prints the list as well.
When Should We Use the nameof Expression?
nameof is not a conversion. The compiler replaces the whole expression with a string literal, so nothing about the enum is looked at while the program runs.
Microsoft’s reference puts it plainly: a nameof expression “is evaluated at compile time and has no effect at run time”.
That is the reason to use it, and the reason it is so limited. We have to name the member in our source, nameof(Country.USA), so it cannot help when the value arrives in a variable, which is most of the time.
The catch that surprises people is what nameof does with a member access. It produces the name of the last identifier in the expression, whatever that identifier happens to be. So nameof(bob.Country) produces "Country", the name of the property, and never "USA", the value the property holds.
Use it where the member is a fixed, known one: a dictionary key, a switch arm, an attribute argument, a log category.
The third way of getting a string from an enum member in C# is by using the nameof expression. Here it is on Bob’s Country property and on the enum member itself:
var bob = new Person("Bob", Country.USA);
var strCountryInstance = nameof(bob.Country);
var strCountryMember = nameof(Country.USA);
Console.WriteLine(strCountryInstance); // Country
Console.WriteLine(strCountryMember); // USA
Considering that, the usage of nameof to get string values of enum members is very specific. When there are so many calls to the ToString() method that it becomes a performance issue, setting the string representation at compile time is a sound choice. But keep in mind that this only works for direct references to the enum member.
If the enum value is stored in a variable, we have to choose one of the previous methods.
How Do We Get a Custom String for an Enum Member?
The built-in options give us the member name exactly as we declared it, or its number. When the string has to look different, we attach an attribute to the member and read it back.
For text meant for a person, [Description] from System.ComponentModel holds the string and a short reflection call retrieves it, through GetField and GetCustomAttribute<DescriptionAttribute>().
For JSON, .NET 9 added [JsonStringEnumMemberName], which System.Text.Json applies itself once the enum is serialized as a string.
The trap is [EnumMember]. It is the attribute people reach for and the one older answers show, and System.Text.Json ignores it. An enum member decorated [EnumMember(Value = "north-america")] still serializes as "NorthAmerica".
Two other serializers do read [EnumMember], each under a condition. DataContractSerializer reads it only if the enum also carries [DataContract], and Newtonsoft.Json writes it only through StringEnumConverter. Neither is the modern JSON stack.
Reflection has a cost: an attribute lookup per call is fine once and wasteful in a loop, so cache the result when the conversion is on a hot path.
Here is a Region enum that carries all three attributes. The [JsonConverter] line tells System.Text.Json to write the enum as a string, and [JsonStringEnumMemberName] does nothing while the enum is written as a number:
using System.ComponentModel;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Region
{
[Description("North America")]
[EnumMember(Value = "north-america")]
NorthAmerica,
[JsonStringEnumMemberName("asia-pacific")]
AsiaPacific,
}
For the rest of what the converter does, see our article on serializing an enum to a string with JsonStringEnumConverter.
To serialize a Region, we give a small Trip class a property of that type:
public class Trip
{
public string Name { get; set; } = string.Empty;
public Region Region { get; set; }
}
Then we serialize one trip for each member:
using System.Text.Json;
var northAmerica = JsonSerializer.Serialize(new Trip { Name = "a", Region = Region.NorthAmerica });
var asiaPacific = JsonSerializer.Serialize(new Trip { Name = "b", Region = Region.AsiaPacific });
Console.WriteLine(northAmerica); // {"Name":"a","Region":"NorthAmerica"}
Console.WriteLine(asiaPacific); // {"Name":"b","Region":"asia-pacific"}
The [Description] text never reaches the JSON. To read it, we find the member’s field and ask it for the attribute:
using System.ComponentModel;
using System.Reflection;
var description = typeof(Region)
.GetField(nameof(Region.NorthAmerica))?
.GetCustomAttribute<DescriptionAttribute>()?
.Description;
Console.WriteLine(description); // North America
GetCustomAttribute<T>() reads other attributes back the same way, and Custom Attributes in C# and .NET walks through declaring and reading our own.
Which Way Should We Convert an Enum to a String?
For almost all code the answer is ToString(), or string interpolation, which gives the same text. It is the shortest thing to write, it needs no type argument, and it returns the member name.
Reach for Enum.GetName<TEnum>() when the value might not be a defined member. It hands back null, which we can fall back from, where ToString() hands back the number as text and looks like a successful conversion.
Use nameof when the member is written out in our source and never varies. It costs nothing at run time because there is no run-time call, and after a rename, any nameof we missed fails to compile, where a hard-coded string would go stale.
Use the non-generic Enum.GetName(Type, object) only when the enum type itself is a variable.
Use an attribute when the string is not ours to choose: a wire format, a display label, anything another system reads. That is also the only case where the member name and the string are allowed to drift apart.
Here are the eight approaches side by side, with what each returns for Country.USA:
| Approach | Call | Returns for Country.USA | Use it when |
|---|---|---|---|
| Instance method | country.ToString() | "USA" | We hold the enum value. This is the default choice |
| Generic lookup | Enum.GetName<Country>(country) | "USA" | We hold a value that may not be a defined member, and we want null rather than a number |
| Non-generic lookup | Enum.GetName(typeof(Country), 0) | "USA" | The enum type is not known until run time |
| Compile-time literal | nameof(Country.USA) | "USA" | The member is named directly in our source. There is no run-time call at all |
| Numeric format | country.ToString("D") | "0" | We want the underlying number as text |
| Hex format | country.ToString("X") | "00000000" | We are writing a value that is read as hex, for example a flags mask |
| Custom text | [Description] plus reflection | whatever the attribute says | The string is for humans and does not match the member name |
| Custom JSON text | [JsonStringEnumMemberName] (.NET 9+) | whatever the attribute says | The string is a JSON wire value |
Every row works on one value at a time. To get every member’s name at once, Enum.GetNames<TEnum>() returns them as an array, and our article on how to enumerate every member of an enum covers other ways to do it.
Conclusion
In this article, we’ve learned why and how to get a string representation of an enum member in C#. We went over three built-in methods of doing it, plus the attributes that supply a custom string, and discussed situations in which to choose one over the other.
Tested with .NET 10.0.10 (SDK 10.0.302).
