Search code examples
c#enumsenum-flagsbitflags

Is there an easier way to parse an int to a generic Flags enum?


I've got a generic function to parse an object into a generic Enum.

However, I'm running into an issue when trying to safely parse an int into a [Flags] Enum.

Directly using Enum.ToObject() works to parse valid combinations, but will just return the original value if there isn't a flag combination.

Additionally, when there's no explicit enum member for a combination of flags, Enum.ToName() and Enum.IsDefined() don't return helpful values.

For Example:

[Flags]
public enum Color
{
   None = 0,
   Red = 1,
   Green = 2,
   Blue = 4,
}

// Returns 20
Enum.ToObject(typeof(Color), 20)

// Returns ""
Enum.ToName(typeof(Color), 3)

// Returns false
Enum.IsDefined(typeof(Color), 3)

I've written a function that I think technically works, but it seems like there has to be a better way to do this.

My Function:

public static T ParseEnumerator<T>(object parseVal, T defaultVal) where T : struct, IConvertible
{
    Type ttype = typeof(T);

    if (!ttype.IsEnum)
    {
        throw new ArgumentException("T must be an enumerated type");
    }

    bool isFlag = ttype.GetCustomAttribute(typeof(FlagsAttribute)) != null;

    try
    {
        if (parseVal == null)
        {
            return defaultVal;
        }
        else if (parseVal is T)
        {
            return (T)parseVal;
        }
        else if (parseVal is string)
        {
            return (T)Enum.Parse(ttype, parseVal.ToString(), true);
        }
//**************** The section at issue **********************************/
        else if (isFlag && parseVal is int)
        {
            List<string> flagsList = new List<string>();
            int maxVal = 0;

            //Loop through each bit flag
            foreach (var val in Enum.GetValues(ttype))
            {
                if (CountBits((int)val) == 1)
                {
                    if ((int)val > maxVal)
                        maxVal = (int)val;

                    // If the current bit is set, add the flag to the result
                    if (((int)parseVal & (int)val) == (int)val)
                    {
                        string enumName = Enum.GetName(ttype, val);
                        if (!string.IsNullOrEmpty(enumName))
                            flagsList.Add(enumName);
                    } 
                }
            }

            // Is the value being parsed over the highest bitwise value?
            if ((int)parseVal >= (maxVal << 1))
                return defaultVal;
            else
                return (T)Enum.Parse(ttype, string.Join(",", flagsList));
        }
//************************************************************************/
        else
        {
            string val = Enum.GetName(ttype, parseVal);
            if (!string.IsNullOrEmpty(val))
                return (T)Enum.ToObject(ttype, parseVal);
            else
                return defaultVal;
        }
    }
    catch
    {
        return defaultVal;
    }
}

Is there something I'm missing? Or is there another way to parse these values safely?

Any help is appreciated, thanks!

-MM


Solution

  • Since your generic function has to know the Enum type to begin with, you can just scrap the function and use basic casting instead.

    using System;
    
    namespace SO_58455415_enum_parsing {
    
        [Flags]
        public enum CheeseCharacteristics {
            Yellow = 1,
            Mouldy = 2,
            Soft = 4,
            Holed = 8
        }
    
        public static class Program {
            static void Main(string[] args) {
                CheeseCharacteristics cc = (CheeseCharacteristics)12;
                Console.WriteLine(cc);
            }
        }
    }
    

    If all you want to know is if you have a value that can be created using the enum flags.. that's pretty easy, as long as we can assume that each flag is "sequential" (e.g. there are no gaps between the flags). All numbers between 1 and the sum of all flag values can be made by some combination of flags. You can simply sum the flag values together and compare that to your question value.

    public static bool IsValidFlags<T>(int checkValue) where T:Enum {
        int maxFlagValue = ((int[])Enum.GetValues(typeof(T))).Sum();
    
        return (0 < checkValue) && (checkValue <= maxFlagValue);
    }
    

    For future reference, you can constrain your generic parameter to an enum:

    void fx<T> () where T:Enum { }