views:

15281

answers:

11
+32  Q: 

C# String enums.

Hi,

I have the following enumerator:

public enum AuthenticationMethod
{
 FORMS = 1,
 WINDOWSAUTHENTICATION = 2,
 SINGLESIGNON = 3
}

The problem however is that I need the word "FORMS" when I ask for AuthenticationMethod.FORMS and not the id 1.

I have found the following solution for this problem:

First I need to create a custom attribute called "StringValue":

public class StringValue : System.Attribute
{
 private string _value;

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

 public string Value
 {
  get { return _value; }
 }

}

Then I can add this attribute to my enumerator:

public enum AuthenticationMethod
{
 [StringValue("FORMS")]
 FORMS = 1,
 [StringValue("WINDOWS")]
 WINDOWSAUTHENTICATION = 2,
 [StringValue("SSO")]
 SINGLESIGNON = 3
}

And off course I need something to retrieve that StringValue:

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

  //Check first in our cached results...

  //Look for our 'StringValueAttribute' 

  //in the field's custom attributes

  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs =
     fi.GetCustomAttributes(typeof(StringValue),
           false) as StringValue[];
  if (attrs.Length > 0)
  {
   output = attrs[0].Value;
  }

  return output;
 }
}

Good now I've got the tools to get a string value for an enumerator. I can then use it like this:

string valueOfAuthenticationMethod = StringEnum.GetStringValue(AuthenticationMethod.FORMS);

Owkay now all of this works like charm but I find it a whole lot of work. I was wondering if there isn't a better solution for this. I also tried something with a dictionary and static properties but that wasn't that good either.

Kind Regards Sem

edit: A lot of solutions are passed on. I'll try them all and then I'll let you guys know what will work out the best. Thx everyone

+35  A: 

Use method

Enum.GetName(Type MyEnumType,  object enumvariable)

as in (Assume Shipper is a defined Enum)

  Shipper x = Shipper.FederalExpress;
  string s = Enum.GetName(typeof(Shipper), x);

There are a bunch of other static methods on the Enum class worth investigating too...

Charles Bretana
Exactly. I did make a custom attribute for a string description, but that's because I want a user-friendly version (with spaces and other special characters) that can be easily bound to a ComboBox or such.
lc
Enum.GetName reflects the field names in the enum - same as the .ToString(). If performance is an issue it can be a problem. I wouldn't worry about it unless you're converting loads of enums though.
Keith
This works like a charm but then I'll have to use the exact values in the enumerator. So I have to say AuthenticationMethod.SSO in stead of AuthenticationMethod.SingleSignOn. But it's clean. Thx.
Sem Dendoncker
Another option to consider, if you need an enum with extra functiuonality, is to "roll yr own" using a struct... you add static readonly named properties to represent the enum values that are initialized to constructors that generate individual instances of the struct...
Charles Bretana
then you can add whatever other struct members you wish, to implement whatever functionality you want this "enum" to have ...
Charles Bretana
The issue here is that GetName is not localizable. That isn't always a concern, but it is something to be aware of.
Joel Coehoorn
+12  A: 

Unfortunately reflection to get attributes on enums is quite slow:

See this question: http://stackoverflow.com/questions/17772

The .ToString() is quite slow on enums too.

You can write extension methods for enums though:

public static string GetName( this MyEnum input ) {
    switch ( input ) {
        case MyEnum.WINDOWSAUTHENTICATION:
            return "Windows";
        //and so on
    }
}

This isn't great, but will be quick and not require the reflection for attributes or field name.

Keith
You can fetch the attribute values once and put them in a Dictionary<MyEnum,string> to keep the declarative aspect.
Jon Skeet
Yeah that's what we ended up doing in an app with lots of enums when we found out that the reflection was the bottle-neck.
Keith
+16  A: 

You can reference the name rather than the value by using ToString()

Console.WriteLine("Auth method: {0}", AuthenticationMethod.Forms.ToString());

The documentation is here:

http://msdn.microsoft.com/en-us/library/16c1xs4z.aspx

...and if you name your enums in Pascal Case (as I do - such as ThisIsMyEnumValue = 1 etc.) then you could use a very simple regex to print the friendly form:

static string ToFriendlyCase(this string EnumString)
{
    return Regex.Replace(EnumString, "(?!^)([A-Z])", " $1");
}

which can easily be called from any string:

Console.WriteLine("ConvertMyCrazyPascalCaseSentenceToFriendlyCase".ToFriendlyCase());

Outputs:

Convert My Crazy Pascal Case Sentence To Friendly Case

That saves running all the way around the houses creating custom attributes and attaching them to your enums or using lookup tables to marry an enum value with a friendly string and best of all it's self managing and can be used on any Pascal Case string which is infinitely more reusable. Of course, it doesn't allow you to have a different friendly name than your enum which your solution does provide.

I do like your original solution though for more complex scenarios though. You could take your solution one step further and make your GetStringValue an extension method of your enum and then you wouldn't need to reference it like StringEnum.GetStringValue...

public static string GetStringValue(this AuthenticationMethod value)
{
  string output = null;
  Type type = value.GetType();
  FieldInfo fi = type.GetField(value.ToString());
  StringValue[] attrs = fi.GetCustomAttributes(typeof(StringValue), false) as StringValue[];
  if (attrs.Length > 0)
    output = attrs[0].Value;
  return output;
}

You could then access it easily straight from your enum instance:

Console.WriteLine(AuthenticationMethod.SSO.GetStringValue());
BenAlabaster
This doesn't help if the "friendly name" needs a space. Such as "Forms Authentication"
Ray Booysen
So make sure the enum is named with caps like FormsAuthentication and insert a space before any caps that aren't at the beginning. It's not rocket science to insert a space in a string...
BenAlabaster
+31  A: 

Try type-safe-enum pattern.

public sealed class AuthenticationMethod {

    private readonly String name;
    private readonly int value;

    public static readonly AuthenticationMethod FORMS = new AuthenticationMethod (1, "FORMS");
    public static readonly AuthenticationMethod WINDOWSAUTHENTICATION = new AuthenticationMethod (2, "WINDOWS");
    public static readonly AuthenticationMethod SINGLESIGNON = new AuthenticationMethod (3, "SSN");        

    private AuthenticationMethod(int value, String name){
        this.name =name;
        this.value = value;
    }

    public override String ToString(){
        return name;
    }

}
Jakub Šturc
+1 works for any string, not just ones which fit the C# rules for namespacing
annakata
It looks like an enum but it isn't an enum. I can imagine that causing some interesting problems if people start trying to compare AuthenticationMethods. You probably need to overload various equality operators too.
Ant
@Ant: I don't have to. Since we have only one instance of each AuthenticationMethod the reference equality inherited from Object works fine.
Jakub Šturc
Oh yes, absolutely right. Very nice solution!
Ant
+1 I've used this solution before. Like a powerful enum. I imagine you cannot use this as values in a switch statement though can you?Also, the only time you would have to worry about the equality problem is if this was declared as a struct rather than a class.
InvisibleBacon
@InvisibleBacon: Right, switch isn't option in this case. However sometimes you can replace switch with polymorphism. See: http://sourcemaking.com/refactoring/replace-conditional-with-polymorphism
Jakub Šturc
This is weird, and seems dangerous to me... What prevents this from happening: AuthenticationMethod a = new AuthenticationMethod(); var b = a.FORMS.SINGLESIGNON; since a.FORMS is itself an instance of AuthenticationMethod?
tyriker
@tyriker: Compiler does. The constructor is private so you cannot create new instance. Also static members are not accessible through instance.
Jakub Šturc
@Jakub Very interesting. I had to play with it to figure out how to use it, and realize its benefits. It's a public, non-static class, but can't be instantiated and you can only access its static members. Basically, it behaves like an enum. But the best part...the static members are typed of the class and not a generic string or int. It's a ... [wait for it] ... type safe enum! Thanks for helping me understand.
tyriker
+7  A: 

I use the Description attribute from the System.ComponentModel namespace. Simply decorate the enum and then use this code to retrieve it:

public static string GetDescription<T>(this object enumerationValue)
            where T : struct
        {
            Type type = enumerationValue.GetType();
            if (!type.IsEnum)
            {
                throw new ArgumentException("EnumerationValue must be of Enum type", "enumerationValue");
            }

            //Tries to find a DescriptionAttribute for a potential friendly name
            //for the enum
            MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
            if (memberInfo != null && memberInfo.Length > 0)
            {
                object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                if (attrs != null && attrs.Length > 0)
                {
                    //Pull out the description value
                    return ((DescriptionAttribute)attrs[0]).Description;
                }
            }
            //If we have no description attribute, just return the ToString of the enum
            return enumerationValue.ToString();

        }

As an example:

public enum Cycle : int
{        
   [Description("Daily Cycle")]
   Daily = 1,
   Weekly,
   Monthly
}

This code nicely caters for enums where you don't need a "Friendly name" and will return just the .ToString() of the enum.

Ray Booysen
+5  A: 

I agree with Keith, but I can't vote up (yet).

I use a static method and swith statement to return exactly what I want. In the database I store tinyint and my code only uses the actual enum, so the strings are for UI requirements. After numerous testing this resulted in the best performance and most control over the output.

public static string ToSimpleString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "ComplexForms";
             break;
     }
}

public static string ToFormattedString(this enum)
{
     switch (enum)
     {
         case ComplexForms:
             return "Complex Forms";
             break;
     }
}

However, by some accounts, this leads to a possible maintenance nightmare and some code smell. I try to keep an eye for enums that are long and a lot of enums, or those that change frequently. Otherwise, this has been a great solution for me.

Tony Basallo
That's kinda what I was suggesting ;-)
Keith
+1  A: 

Option 1:

public sealed class FormsAuth
{
     public override string ToString{return "Forms Authtentication";}
}
public sealed class WindowsAuth
{
     public override string ToString{return "Windows Authtentication";}
}

public sealed class SsoAuth
{
     public override string ToString{return "SSO";}
}

and then

object auth = new SsoAuth(); //or whatever

//...
//...
// blablabla

DoSomethingWithTheAuth(auth.ToString());

Option 2:

public enum AuthenticationMethod
{
        FORMS = 1,
        WINDOWSAUTHENTICATION = 2,
        SINGLESIGNON = 3
}

public class MyClass
{
    private Dictionary<AuthenticationMethod, String> map = new Dictionary<AuthenticationMethod, String>();
    public MyClass()
    {
         map.Add(AuthenticationMethod.FORMS,"Forms Authentication");
         map.Add(AuthenticationMethod.WINDOWSAUTHENTICATION ,"Windows Authentication");
         map.Add(AuthenticationMethod.SINGLESIGNON ,"SSo Authentication");
    }
}
pablito
+2  A: 

I use a combination of several of the suggestions above, combined with some caching. Now, I got the idea from some code that I found somewhere on the net, but I can neither remember where I got it or find it. So if anyone ever finds something that looks similar please comment with the attribution.

Anyway, the usage involves the type converters, so if you are binding to the UI it 'just works'. You can extended with Jakub's pattern for quick code lookup by initializing from the type converter into the static methods.

The base usage would look like this

[TypeConverter(typeof(CustomEnumTypeConverter(typeof(MyEnum))]
public enum MyEnum
{
  // The custom type converter will use the description attribute
  [Description("A custom description")]
  ValueWithCustomDescription,

  // This will be exposed exactly.
  Exact
}

The code for the custom enum type converter follows:

public class CustomEnumTypeConverter<T> : EnumConverter
    where T : struct
{
    private static readonly Dictionary<T,string> s_toString = 
      new Dictionary<T, string>();

    private static readonly Dictionary<string, T> s_toValue = 
      new Dictionary<string, T>();

    private static bool s_isInitialized;

    static CustomEnumTypeConverter()
    {
        System.Diagnostics.Debug.Assert(typeof(T).IsEnum,
          "The custom enum class must be used with an enum type.");
    }

    public CustomEnumTypeConverter() : base(typeof(T))
    {
        if (!s_isInitialized)
        {
            Initialize();
            s_isInitialized = true;
        }
    }

    protected void Initialize()
    {
        foreach (T item in Enum.GetValues(typeof(T)))
        {
            string description = GetDescription(item);
            s_toString[item] = description;
            s_toValue[description] = item;
        }
    }

    private static string GetDescription(T optionValue)
    {
        var optionDescription = optionValue.ToString();
        var optionInfo = typeof(T).GetField(optionDescription);
        if (Attribute.IsDefined(optionInfo, typeof(DescriptionAttribute)))
        {
            var attribute = 
              (DescriptionAttribute)Attribute.
                 GetCustomAttribute(optionInfo, typeof(DescriptionAttribute));
            return attribute.Description;
        }
        return optionDescription;
    }

    public override object ConvertTo(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, 
       object value, Type destinationType)
    {
        var optionValue = (T)value;

        if (destinationType == typeof(string) && 
            s_toString.ContainsKey(optionValue))
        {
            return s_toString[optionValue];
        }

        return base.ConvertTo(context, culture, value, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, 
       System.Globalization.CultureInfo culture, object value)
    {
        var stringValue = value as string;

        if (!string.IsNullOrEmpty(stringValue) && s_toValue.ContainsKey(stringValue))
        {
            return s_toValue[stringValue];
        }

        return base.ConvertFrom(context, culture, value);
    }
}

}

Steve Mitcham
+2  A: 

When I'm confronted with this problem, there are a couple of questions that I try to find the answers to first:

  • Are the names of my enum values sufficiently friendly for the purpose, or do I need to provide friendlier ones?
  • Do I need to round-trip? That is, will I need to take text values and parse them into enum values?
  • Is this something I need to do for many enums in my project, or just one?
  • What kind of UI elements will I be presenting this information in - in particular, will I be binding to the UI, or using property sheets?
  • Does this need to be localizable?

The simplest way to do this is with Enum.GetValue (and support round-tripping using Enum.Parse). It's also often worth building a TypeConverter, as Steve Mitcham suggests, to support UI binding. (It's not necessary to build a TypeConverter when you're using property sheets, which is one of the nice things about property sheets. Though lord knows they have their own issues.)

In general, if the answers to the above questions suggest that's not going to work, my next step is to create and populate a static Dictionary<MyEnum, string>, or possibly a Dictionary<Type, Dictionary<int, string>>. I tend to skip the intermediate decorate-the-code-with-attributes step because what's usually coming down the pike next is the need to change the friendly values after deployment (often, but not always, because of localization).

Robert Rossney
+2  A: 

If you think about the problem we're trying to solve, it's not an enum we need at all. We need an object that allows a certain number of values to be associated with eachother; in other words, to define a class.

Jakub Šturc's type-safe enum pattern is the best option I see here.

Look at it:

  • It has a private constructor so only the class itself can define the allowed values.
  • It is a sealed class so values can't be modifed through inheritence.
  • It is type-safe, allowing your methods to require only that type.
  • There is no reflection performance hit incurred by accessing the values.
  • And lastly, it can be modified to associate more than two fields together, for example a Name, Description, and a numeric Value.
Harvo
A: 

based on the MSDN: http://msdn.microsoft.com/en-us/library/cc138362.aspx foreach (string str in Enum.GetNames(typeof(enumHeaderField))) { Debug.WriteLine(str); }

str will be the names of the fields

junior software