Schultz9999
Schultz9999

Reputation: 8926

A way to parse .NET enum string or int value attributed with 'Flags'

There is a nice way of figuring out the enumeration element using the following approach:

// memberType is enum type
if (Enum.IsDefined(memberType, valueString))
{
    return Enum.Parse(memberType, valueString);
}
else
{
    try 
    {
        var underlyingValue = Convert.ChangeType(valueString, Enum.GetUnderlyingType(memberType));
        if (Enum.IsDefined(memberType, underlyingValue))
        {
            return underlyingValue;
        }
    }
    catch...
}

This works like charm. Except for values built from enumerations marked with FlagsAttribute. For example, for this enum and a value:

[Flags]
enum MyEnum {
    One = 0x1,
    Two = One << 1,
    Four = One << 2,
    Eight = One << 3
}

var e = MyEnum.One | MyEnum.Eight;

the approach above doesn't work. Looks like the only way to make it work is to try to get all the enum values and bitwise AND them with the input value. That's somewhat tedious though. So do you know any better way?

Answer:

The final method looks like this:

var parsed = Enum.Parse(memberType, valueString);
decimal d;
if (!decimal.TryParse(parsed.ToString(), out d))
{
    return parsed;
}
throw new ArgumentOutOfRangeException(memberInfo.Name, valueString, "Bad configuration parameter value.");

Upvotes: 15

Views: 4323

Answers (5)

driis
driis

Reputation: 164291

Use Enum.Parse. Then check on the returned value, that no bits are set that are not valid flags. To do this, make a bitmask containing all the valid values, and OR them together with the value. If the result differs from the mask, some bits was set, that are not valid flags. This is not too tedious (but you will need to check whether the Enum is in fact a Flags enum, before applying this logic - if it is not a Flags enum, use IsDefined).

The code could go something like this; and you could store the mask per type pre-computed if you think it might be too expensive to calculate each time (it's probably not, but will depend on your case):

object value = Enum.Parse(memberType, valueString);
int numericValue = (int)value;
int definedMask = Enum.GetValues(memberType).Cast<int>().Aggregate(0, (v,a) => v | a);
if ((definedMask | numericValue) != definedMask)
    throw new InvalidOperationException(String.Format("{0} is not a valid {1} value.", valueString, memberType.Name));
return value;

Upvotes: 1

SwDevMan81
SwDevMan81

Reputation: 49978

I guess a better question to ask, how to detect bad values.

Looks like there is a nice work around found in C# 4.0 in a Nutshell. From here. Once you Parse the integer value to the enum, you can use this and see if the value is valid. This will work for combined flags.

static bool IsFlagDefined(Enum e)
{
    decimal d;
    return !decimal.TryParse(e.ToString(), out d);
}

Upvotes: 4

The Smallest
The Smallest

Reputation: 5773

Why don't you just use Enum.Parse()

var test = MyEnum.One | MyEnum.Eight;
var str = test.ToString();
var back = (MyEnum) enum.Parse(typeof(MyEnum), str); // this returns MyEnum.One | MyEnum.Eight

First try to convert your string to "undelying type", if succeded - cast it to MyEnum and that's what you needed. If convert failed - try to use Enum.Parse. If it also fails - that is a very bad input (throw exception)


Update: No testing for int conversion is needed enum.Parse(typeof(MyEnum), "9") returns MyEnum.One | MyEnum.Eight (tested in Framework 2.0 to 4.0)


Update 2: So question really was "How to figure out bad values?". Dirty solution:

var res = Enum.Parse(targetType, str);
bool isBad;
try
{
    Convert(res.ToString(), underType);
    isBad = true;
}
catch
{
    // convert failed
    isBad = false;
}

if (isBad)
    throw new Exeption();
return res;

But it is very dirty and throwing exception is expensive operation so there is performance penalty here... But it works)

Upvotes: 1

user726024
user726024

Reputation:

That is a nice question, your suggestion

try to get all the enum values and bitwise AND them with the input value

after 10 minutes of research looks really reasonable.. ^^

I found a nice link regarding similar issue, a guy there spent quite some time to write this article, maybe you will find it interesting too.

Breaking down C# Flags enums into individual values for comparison

Upvotes: 0

Gregory A Beamer
Gregory A Beamer

Reputation: 17010

This is expected behavior, as you can get values that do not correspond to the flags. For example, let's assume value a = 1, b = 2, c = 4, d= 8, etc. (Just standard binary progression). It is possible to have a 5 for the value (a & c) or 7 (a, b & c).

Upvotes: 3

Related Questions