0

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

M. McCrary
  • 35
  • 3

1 Answers1

1

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 { }
Sam Axe
  • 33,313
  • 9
  • 55
  • 89
  • This runs into the same issue as Enum.ToObject(). if we change the 12 in your example to a 20, the result would be "20" I appreciate the note about constraining the generic to an Enum. I had only seen it done the way I have, so I assumed that's what you had to do. – M. McCrary Oct 18 '19 at 20:37
  • @M.McCrary: So what would you expect a value of `20` to turn into? An exception? – Sam Axe Oct 18 '19 at 20:43
  • Yes, an exception would ideally be thrown since its not a valid flag combination. I know that's not how Enums generally work, that's why for normal Enums I check if the GetName() value exists before casting like usual. But for the case of Flag Enums the GetName() and IsDefined() values are not helpful. – M. McCrary Oct 21 '19 at 19:04
  • @M.McCrary: I updated my answer. You should be able to modify it to throw an exception or take any other action you like. – Sam Axe Oct 21 '19 at 21:21
  • Yeah, this is likely as good as it gets with what I want to do. – M. McCrary Oct 22 '19 at 14:44