none
Using String Enums in switch RRS feed

  • Question

  • I have Two Helper Classes to get a String attribute from a enum.

    internal class StringValue : System.Attribute    {        private readonly string _value;        public StringValue(string value)        {            _value = value;        }        public string Value        {            get { return _value; }        }
        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;
            }
        }

    And my enum is:

        internal enum DataSourceTypes
        {
            [StringValue("ControlDataSource")]
            RCDATASOURCE = 1,
            [StringValue("sysDataSource")]
            TVSYSTEMDATASOURCE = 2,
        }

    I need to use in a Switch case on my Controller like this:

     switch (dataSource.ToLower())
                {
                    case StringEnum.GetStringValue(DataSourceTypes.RCDATASOURCE):
                        {
                            model.Configuration = "sysDataSource";
                            model.DataSource = "sysDataSource";
                            model.DataBinderType = "sysDataSource";
                            model.ScriptSource = "~/Scripts/index.js";
                            model.ScriptAction = "TVSystemDataAction";
                            model.ScriptFunctionName = "TVSystemInfo";
                            break;
                        }
                    case "controldatasource":
                        {
                            model.Configuration = "ControlDataSource";
                            model.DataSource = "ControlDataSource";
                            model.DataBinderType = "ControlDataSource";
                            model.ScriptSource = "~/Scripts/index.js";
                            model.ScriptAction = "ConfigurableRemoteDataAction";
                            model.ScriptFunctionName = "RemoteControlInfo";
                            break;
                        }
                    //RCDataSource.js
                    default:
                        {
                            LoggerManager.Log(LoggerType.MAIN, LogLevel.Error, null, null, "[RemoteDataSourceController] DataSource does not supported (Data Source: "
                                + dataSource + ")");
                            return new HttpStatusCodeResult(404);
                            break;
                        }
                }

    I get a error "A constant value is expected" in 

    case StringEnum.GetStringValue(DataSourceTypes.RCDATASOURCE):

    There is another way to use something like this?

    Thanks in advance


    Wednesday, April 18, 2018 11:02 AM

Answers

  • Personally I would recommend against creating your own attribute and simply use Description (or perhaps DisplayName). This is how we've done it for our needs.

    As for the switch statement, you need a constant so use the enum values in the case and do the conversion on the expression.

    if (Enum.TryParse(dataSource, out DataSourceTypes sourceType))
    {
       switch(sourceType)
       {
          case DataSourceTypes.RCDATASOURCE: ...
          ...
          default: //Unknown type
       };
    };

    Now to get the parsing to work you could do all this in your code but I prefer to use type converters. That is what they are designed for and it allows your code to be called by other areas of the framework automatically. Here I have a version that I've been using in code for years that works with any Enum. To add support for Description you'd just need to modify the FromString method.

    public class EnumWithDescriptionTypeConverter<T> : EnumTypeConverter<T> where T : struct
    {      
        protected override bool FromString ( ITypeDescriptorContext context, CultureInfo culture, string str, out T result )
        {
            //Try the standard conversion
            if (base.FromString(context, culture, str, out result))
                return true;
    
            //Should probably do this once and store in a dictionary for later - perhaps in the cctor
            var fields = typeof(T).GetFields();
            foreach (var field in fields)
            {
                var attrs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);
                foreach (DescriptionAttribute attr in attrs)
                {
                    if (String.Compare(attr.Description, str, true) == 0)
                    {
                        result = (T)field.GetValue(null);
                        return true;
                    };
                };
            };
    
            result = default(T);
            return false;
        }
    }

    Finally you just need to annotate your enum. Personally I like to create a custom type converter for each enum so: a) it stands out, b) I can make additional customizations but it isn't technically necessary.

    public sealed class DataSourceTypes : EnumTypeConverter(typeof(DataSourceTypes)>

    [TypeConverter(typeof(StringValueEnumTypeConverter))] internal enum DataSourceTypes { ... }

    Now not only will the earlier code work but any time .NET needs to convert to or from your enum to a string it will automatically do what you want it to do. Unfortunately however Enum.Parse/TryParse does not use the TypeConverter by default. Personally I created extension methods to handle this for my code but the equivalent would be this.

    var converter = TypeDescriptor.GetConverter(typeof(DataSourceTypes));
    
    var type1 = converter.ConvertFromString("controlDataSource");
    var type2 = converter.ConvertFromString("sysDataSource");
    var type3 = converter.ConvertFromString("RCDATASOURCE");
    var type4 = converter.ConvertFromString("blah");


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, April 18, 2018 3:58 PM
    Moderator

All replies

  • First off, instead of using 

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

    You could do

    attr = fi.GetCustomAttribute<StringValue>();

    Since you use false on the second argument, you expect only 1 Attribute of that type, so you can get it through reflection directly, instead of getting a list. null is returned if it does not exist on the Property.

    Second:

    case StringEnum.GetStringValue(DataSourceTypes.RCDATASOURCE):

    is not a static value, in switch-case you cannot use dynamic values, since you call a method, the value is dynamic and hence cannot be used

    Third, i think you can simplify it all with Enum.Parse or Enum.TryParse wich converts string to enum values. Meaning to remove the StringValueAttribute completely and renaming your enum to use the values you put in with your StringValueAttribute. (Hint, Enum.Parse and TryParse can also do ignore case...)

    Where does your dataSource value come from?


    Please be so kind to close your Threads when you found an answer, these Threads should help everyone with similar issues.
    You can close a Thread via the"Mark as Answer" link below posts. You can mark your own posts as answers if you were not helped out but found a solution, in such a case, please provide the answer.
    Happy coding
    PS: I assure everyone that I did not ever had the desire to offend anyone.




    • Edited by MDeero Wednesday, April 18, 2018 11:46 AM
    Wednesday, April 18, 2018 11:31 AM
  • The error message is quite descriptive that the values for switch statement should be constant not something that gets evaluated on run-time.

    Please refer to the following post for details:

    Why can we only use constants in a switch-case statement?

    Hope it helps!


    [If a post helps to resolve your issue, please click the "Mark as Answer" of that post or click Answered "Vote as helpful" button of that post. By marking a post as Answered or Helpful, you help others find the answer faster. ]


    Blog | LinkedIn | Stack Overflow | Facebook
    profile for Ehsan Sajjad on Stack Exchange, a network of free, community-driven Q&A sites

    Wednesday, April 18, 2018 11:41 AM
  • the var datasource comes from a parameter in the function. is a String "controldatasource" or "sysdatasource"

    can you explain better how to use your third point?

    Wednesday, April 18, 2018 1:57 PM
  • void someFunction(string someEnumRepresentation) {
        SomeEnum value;
        bool ignoreCase = true;
        if(Enum.TryParse<SomeEnum>(someEnumRepresentation, ignoreCase, out value))
        {
            switch(value)
            {
                case SomeEnum.SomeValue:
                    // Some logic for enum value
                    break;
            }
            // Some method logic
        }
    }
    
    enum SomeEnum { SeomValue }

    someEnumRepresentation := "somevalue" or "SOMEVALUE" or any other cases

    Instead of trying to decorate your Enum, which is just a way to provide the programmer with the possibility to use names instead of Integers (or other numeric types) which are easier to remember, with alternative names for your values, use the alternative names and the Enum.TryParse or Enum.Parse methods.

    You are projecting a string to an int with an enum, and then with the StringValue attribute you do it AGAIN but with a different name... why. Just use the name thats convenient as you already have the option to Decorate the enums with StrinValueAttribute, you can just go and change their names.

    Even better would be to create an overlaod method accepting a string and one accepting the actual Enum. The overload accepting the string only parses the value and calls the overload which accepts the enum.

    Or, if possible (and if it is, do this) just dont accept a string instead of an enum at all. If your value comes from Web, you can probably use a JSON object (or any other dto format) and parse that directly, JsonConvert for example (newtonsoft) does support using enum names (so strings) instead of enum values (as ints or so). I think most parsers / serializers do. A console application ofc does not, then you would use the enum.parse approach, in any other case i would let the serializer do the work.

    Another side affect in your scenario would be, that you can use value.ToString to assign the model.Configuration etc. which reduces the switch case blocks and hence increases readability.

    Though seeing that those Properties get assigned with a string representing the same name as the enum value, you might want to consider changing them to use a enum as well and might even remove some of the properties.


    Please be so kind to close your Threads when you found an answer, these Threads should help everyone with similar issues.
    You can close a Thread via the"Mark as Answer" link below posts. You can mark your own posts as answers if you were not helped out but found a solution, in such a case, please provide the answer.
    Happy coding
    PS: I assure everyone that I did not ever had the desire to offend anyone.



    • Edited by MDeero Wednesday, April 18, 2018 2:47 PM
    Wednesday, April 18, 2018 2:35 PM
  • Hi Fabio,

    You are not the first one. But an Enum is not a data piece. It is an old program language documentation extension for the programmer, which is still in many program languages.

    Try to avoid to use it as data, there are to many good easier ways currently in all the generic classes.  It is not impossible to use, but then it is not a horse behind the cart, but one who has its face in the opposite direction. You need endless code which nobody understand when there is maintenance needed. 


    Success
    Cor

    Wednesday, April 18, 2018 3:39 PM
  • Personally I would recommend against creating your own attribute and simply use Description (or perhaps DisplayName). This is how we've done it for our needs.

    As for the switch statement, you need a constant so use the enum values in the case and do the conversion on the expression.

    if (Enum.TryParse(dataSource, out DataSourceTypes sourceType))
    {
       switch(sourceType)
       {
          case DataSourceTypes.RCDATASOURCE: ...
          ...
          default: //Unknown type
       };
    };

    Now to get the parsing to work you could do all this in your code but I prefer to use type converters. That is what they are designed for and it allows your code to be called by other areas of the framework automatically. Here I have a version that I've been using in code for years that works with any Enum. To add support for Description you'd just need to modify the FromString method.

    public class EnumWithDescriptionTypeConverter<T> : EnumTypeConverter<T> where T : struct
    {      
        protected override bool FromString ( ITypeDescriptorContext context, CultureInfo culture, string str, out T result )
        {
            //Try the standard conversion
            if (base.FromString(context, culture, str, out result))
                return true;
    
            //Should probably do this once and store in a dictionary for later - perhaps in the cctor
            var fields = typeof(T).GetFields();
            foreach (var field in fields)
            {
                var attrs = field.GetCustomAttributes(typeof(DescriptionAttribute), false);
                foreach (DescriptionAttribute attr in attrs)
                {
                    if (String.Compare(attr.Description, str, true) == 0)
                    {
                        result = (T)field.GetValue(null);
                        return true;
                    };
                };
            };
    
            result = default(T);
            return false;
        }
    }

    Finally you just need to annotate your enum. Personally I like to create a custom type converter for each enum so: a) it stands out, b) I can make additional customizations but it isn't technically necessary.

    public sealed class DataSourceTypes : EnumTypeConverter(typeof(DataSourceTypes)>

    [TypeConverter(typeof(StringValueEnumTypeConverter))] internal enum DataSourceTypes { ... }

    Now not only will the earlier code work but any time .NET needs to convert to or from your enum to a string it will automatically do what you want it to do. Unfortunately however Enum.Parse/TryParse does not use the TypeConverter by default. Personally I created extension methods to handle this for my code but the equivalent would be this.

    var converter = TypeDescriptor.GetConverter(typeof(DataSourceTypes));
    
    var type1 = converter.ConvertFromString("controlDataSource");
    var type2 = converter.ConvertFromString("sysDataSource");
    var type3 = converter.ConvertFromString("RCDATASOURCE");
    var type4 = converter.ConvertFromString("blah");


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, April 18, 2018 3:58 PM
    Moderator