15

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.");
Schultz9999
  • 8,717
  • 8
  • 48
  • 87

5 Answers5

4

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);
}
Community
  • 1
  • 1
SwDevMan81
  • 48,814
  • 22
  • 151
  • 184
  • This might be the most elegant way to detect this, but I would make sure to comment that method so future maintainers can quickly figure out how it works ! :-) – driis Apr 26 '11 at 20:32
  • 1
    We have a winner :) I would go as far as marking my post as a dup actually. http://stackoverflow.com/questions/4950001/enum-isdefined-with-flagged-enums – Schultz9999 Apr 26 '11 at 20:37
3

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).

Gregory A Beamer
  • 16,870
  • 3
  • 25
  • 32
  • @Schultz9999 - In his example, you have no 5 or 7; so what should it return? – John Kraft Apr 26 '11 at 19:55
  • Since a flag can mean a&&b or a&&d, it has values that cannot be directly turned into a single value, as your first example, because it is more than one value. This is why you need bitwise operations. if ((value && a) == a) if 10000001 && 00000001 = 00000001 - yes, it does, as that bitmask (logical and) is 00000001. Note that I have both flag a and h set (10000001). I cannot turn that value into a single parsed value, so I have to test all of the values I am interested in. – Gregory A Beamer Apr 26 '11 at 20:02
  • @Gregory A Beamer: no doubt. That's exactly what I mean in the end of my post, which I am referencing as 'tedious'. – Schultz9999 Apr 26 '11 at 20:05
  • @Shultz9999: Flags don't need to have only one bit set. What should `Three & Five` return? `One` or `Three & Five`? What if there is no `One`? – BlueRaja - Danny Pflughoeft Apr 26 '11 at 20:17
  • @John Kraft: it should fail actually. – Schultz9999 Apr 26 '11 at 20:18
  • @BlueRaja - Danny Pflughoeft: only power of 2 are good values for flags. – Schultz9999 Apr 26 '11 at 20:19
1

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)

The Smallest
  • 5,713
  • 25
  • 38
  • I'd love to use explicit types but I can't because it's a reflection based code where the type comes from the member info. The values are coming from an external source (like DB or file). In order to initialize a class, the logic above is used. Coming back to your question by not just Parse? Strings like "MyEnum.One | MyEnum.Eight" may work but what about ints? Did you try 1 + 8 = 9? I see 9 as the result, not One | Eight. – Schultz9999 Apr 26 '11 at 20:00
  • 3
    I tried `(MyEnum) Enum.Parse(typeof(MyEnum), "9")` - it returned `One|Eight` – The Smallest Apr 26 '11 at 20:04
  • It also seems to me the the built-in `Enum.Parse` does the right thing here. @Schultz9999, can you provide us with a case where that is not the case. – driis Apr 26 '11 at 20:08
  • My bet. I guess a better question to ask, how to detect bad values. Because 9 is good but 100 is not and Parse will not fail. That's why I am using IsDefined. – Schultz9999 Apr 26 '11 at 20:12
1

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;
driis
  • 161,458
  • 45
  • 265
  • 341
  • It looks good. Converting to int is not pretty (because enum could be type specified) but it still should do it. If this check passes, Parse can be used to get the value of the mask. – Schultz9999 Apr 26 '11 at 20:32
0

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

  • That's for the link. Just a short note. There is plenty of straightforward/unnecessary code there that can be contracted into one like LINQ statements. It's somewhat confusing why LINQ is used in some places and not in others. – Schultz9999 Apr 26 '11 at 20:27