Click here to Skip to main content
15,881,248 members
Articles / Programming Languages / C#
Article

String Enumerations in C#

Rate me:
Please Sign up or sign in to vote.
4.47/5 (49 votes)
27 Jul 2005CPOL4 min read 729.1K   13.5K   136   44
Options for declaring and referencing static string data.

Introduction

The idea of string enumerations in C# (and the lack thereof), has bothered me for a while. Enum's are useful things but only allow the storage of numeric types. What if you want to use strings?

Background

It struck me that I had a number of requirements when using string 'constants'.

I wanted the ability to define the values in-line and reference them like an enum value - e.g. MyEnum.MyString. It would also be nice to be able to reference the string values as a particular enum type, for the purposes of validation and strong-typing.

Options

Constants

The first thing I naturally gravitated towards was constants defined in a specific class. The following shows an example of this:

C#
public sealed class MyConsts
{

    private MyConsts() {}

    public const string Val1 = "MyVal1";
    public const string Val2 = "MyVal2";
    public const string Val3 = "MyVal3";

}

The constants can be easily accessed as MyConsts.Val1 etc. This is extremely simple and reasonably neat. As these are constants the values are compiled in and as such can also be used nicely in switch statements.

Constants do have an interesting side effect in that the values are actually copied to any client code that uses the class. This means that client assemblies could potentially have out of date values if the values in the constants assembly were to change and it were redeployed without a client rebuild.

A possibly preferable alternative to the class would be to use a struct to house the constants, as this would always be created on the stack rather than the heap.

Static readonly

readonly values can be initialized with a value and changed only within a class's constructor. This effectively means their eventual value is not known at compile time. The code sample above can be changed slightly to illustrate this 'pseudo const'.

C#
public sealed class MyConsts
{

    private MyConsts() {}

    public static readonly string Val1 = "MyVal1";
    public static readonly string Val2 = "MyVal2";
    public static readonly string Val3 = "MyVal3";
}

Values are again accessed in exactly the same way e.g. MyConsts.Val3, but as the values are conceptually dynamic, they will always be accessed from the class where they are defined. This does also mean that cannot be evaluated in switch statements.

Enums

Enum's can only contain numeric values right? Well, each value can actually be decorated with custom attributes, which means it's possible to extend a normal enum to contain string values as follows:

C#
public enum HandTools
{
    [StringValue("Cordless Power Drill")
    Drill = 5,
    [StringValue("Long nose pliers")
    Pliers = 7,
    [StringValue("20mm Chisel")
    Chisel = 9

}

Enums can of course be declared without explicit numeric values, in which case each item will have implicit numeric items (starting from 0). This can be a bit of a pain when debugging (and provides a slight performance hit), so Microsoft suggests always declaring a numeric value.

The StringValue attribute is a simple attribute class (derived from System.Attribute) that is constructed with a string and exposes that string via a single Value property:

C#
public class StringValueAttribute : System.Attribute
{

    private string _value;

    public StringValueAttribute(string value)
    {
        _value = value;
    }

    public string Value
    {
    get { return _value; }
    }

}

We now need some way of accessing these strings (via the StringValue attribute) and using them as part of our standard enum type.

StringEnum

The StringEnum class acts as a wrapper for string value access in enumerations. It assumes that enums wishing to expose string values do so via the StringValue attribute. The StringEnum class has static and instance portions and provides the following static methods:

  • Parse : Parse a string value and return the corresponding enum value.
  • GetStringValue : Return the string value associated with the given enum value.
  • IsStringValueDefined : Indicate the existence or non-existence of an enum value with the given string value.

The GetStringValue is shown below. This takes in an enum value and performs a fast type lookup before retrieving the StringValue attribute (if it can find it). The string value is then returned. We use a static Hashtable (_stringValues) to cache results (keyed by enum value) to avoid the minor reflection performance hit on subsequent requests.

C#
public static string GetStringValue(Enum value)
{
    string output = null;
    Type type = value.GetType();

    //Check first in our cached results...
    if (_stringValues.ContainsKey(value))
      output = (_stringValues[value] as StringValueAttribute).Value;
    else 
    {
        //Look for our 'StringValueAttribute' 
        //in the field's custom attributes
        FieldInfo fi = type.GetField(value.ToString());
        StringValueAttribute[] attrs = 
           fi.GetCustomAttributes(typeof (StringValueAttribute), 
                                   false) as StringValueAttribute[];
        if (attrs.Length > 0)
        {
            _stringValues.Add(value, attrs[0]);
            output = attrs[0].Value;
        }
    }

    return output;
}

Case-insensitive overloads are provided for the Parse and IsStringValueDefined methods.

Similar instance methods are provided (using a constructor taking the enum's type), with an additional method for use in data binding:

  • GetListValues : Return an IList for data binding the StringEnum values.

Using the code

The code can be simply used by extending any enum to include [StringValue("")] attributes. You can then use the StringEnum class to retrieve the values as follows:

C#
//Values intentionally unordered...

public enum CarType
{
    [StringValue("Saloon / Sedan")] Saloon = 5,
    [StringValue("Coupe")] Coupe = 4,
    [StringValue("Estate / Wagon")] Estate = 6,
    [StringValue("Hatchback")] Hatchback = 8,
    [StringValue("Utility")] Ute = 1,
}


public void TestMethod()
{
    MessageBox.Show(StringEnum.GetStringValue(CarType.Estate));
    //The line above shows 'Estate / Wagon'

    MessageBox.Show(StringEnum.Parse(typeof(CarType), 
                     "estate / wagon", true).ToString());
    //The line above converts back to an enum 
    //value from String Value (case insensitive)
    //and shows 'Estate'

    int enumValue = (int)StringEnum.Parse(typeof(CarType), 
                                     "estate / wagon", true);
    MessageBox.Show(enumValue.ToString());
    //The line above does the same again but this time shows 
    //the numeric value of the enum (6)
}

Points of interest

The source project has a full set of NUnit tests to illustrate each method, and the demo project shows other items such as examples of data binding with StringEnum.

Have fun!

History

  • (29/07/2005) - Updated source zip file with hashtable caching for GetStringValue.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Codebureau
Australia Australia
Started with COBOL/CICS/DB2 - ended up with C#/ASP.NET. I'm a half-geek (or so my wife tells me!)

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ian Bedson11-Jan-17 14:51
professionalIan Bedson11-Jan-17 14:51 
QuestionMy Vote of 5 Pin
Mohit-Nagarro10-Nov-14 22:16
Mohit-Nagarro10-Nov-14 22:16 
QuestionWhat about resources change at runtime? Pin
MongoFan9-Apr-14 0:17
MongoFan9-Apr-14 0:17 
First of all thanks for the good idea.
The problem of using Attributes in enum with an constant string is,
that you can not change the string resources at runtime.

f.e. If you use
C#
MessageBox.Show(StringEnum.GetStringValue(CarType.Estate));

Use will get "Estate / Wagon", but if you want to change the language you want
f.e. "Besitz / Wagen"

Is there any possiblity to use something like:
C#
[StringValue(Strings.MyEstateProperty)];


or any other ideas?
GeneralMy vote of 5 Pin
udx00119-Jun-13 5:40
udx00119-Jun-13 5:40 
SuggestionNice trick ... if you are lazy like me... Pin
neoDD6912-Jun-13 2:13
neoDD6912-Jun-13 2:13 
GeneralNice Pin
JohnDetroit7-Jun-12 4:33
JohnDetroit7-Jun-12 4:33 
QuestionI've wrote an article related to this context. Pin
ShlomiO2-Jun-12 3:27
ShlomiO2-Jun-12 3:27 
GeneralMy vote of 4 Pin
InfernalBlackNecroCorpseSodomy400028-May-12 16:31
InfernalBlackNecroCorpseSodomy400028-May-12 16:31 
QuestionNot able to run this project Pin
baskaran chellasamy10-May-12 5:32
baskaran chellasamy10-May-12 5:32 
Questionnice article Pin
Rupesh Kumar Tiwari24-Feb-12 4:56
Rupesh Kumar Tiwari24-Feb-12 4:56 
QuestionCode license Pin
sanurss28-Oct-11 9:07
sanurss28-Oct-11 9:07 
AnswerRe: Code license Pin
CodeBureau - Matt Simner7-Nov-11 9:50
CodeBureau - Matt Simner7-Nov-11 9:50 
GeneralMy vote of 4 Pin
ymanzon17-Jun-11 11:13
ymanzon17-Jun-11 11:13 
GeneralWhat's wrong with the first two options Pin
MartinFister18-Apr-10 7:39
MartinFister18-Apr-10 7:39 
GeneralRe: What's wrong with the first two options Pin
charlieyen28-Jan-12 14:12
charlieyen28-Jan-12 14:12 
GeneralIf using .NET 3.5+ [modified] Pin
Joe Gershgorin1-Apr-10 17:00
Joe Gershgorin1-Apr-10 17:00 
GeneralMisuse of sealed class Pin
Sergey Alexandrovich Kryukov11-Jan-10 6:07
mvaSergey Alexandrovich Kryukov11-Jan-10 6:07 
GeneralGeneric Parse Pin
psay27-Oct-09 17:36
psay27-Oct-09 17:36 
GeneralRe: Generic Parse Pin
s_sagar4u14-Sep-15 0:15
s_sagar4u14-Sep-15 0:15 
GeneralYou have a good base, now think about how to add behaviors. Pin
Clinton Sheppard24-Sep-09 5:40
Clinton Sheppard24-Sep-09 5:40 
GeneralToString() example, better fit for my situation ... Pin
wardj21-Aug-09 4:04
wardj21-Aug-09 4:04 
GeneralRe: ToString() example, better fit for my situation ... Pin
CodeBureau - Matt Simner21-Aug-09 12:36
CodeBureau - Matt Simner21-Aug-09 12:36 
Generalcould be more easy to use [modified] Pin
Asif Ashraf29-Mar-09 2:11
professionalAsif Ashraf29-Mar-09 2:11 
GeneralRe: could be more easy to use [modified] Pin
David Logan29-Mar-09 13:52
David Logan29-Mar-09 13:52 
GeneralRe: could be more easy to use [modified] Pin
Joe Gershgorin1-Apr-10 11:27
Joe Gershgorin1-Apr-10 11:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.