3

I need to get the numeric position of an enum in its definition. Consider the following enum - it is used for bit fields but the status names would be useful if they had the values on the right that I have commented.

[Flags]
public enum StatusFlags
{
    None = 0,                 // 0  -- these commented indexes are the numbers I also would like
    Untested = 1,             // 1     to associate with the enum names.
    Passed_Programming = 2,   // 2
    Failed_Programming = 4,   // 3
    // ... many more
}

I have created a static method as follows, which works for what I want.

public static int GetStatusID(this StatusFlags flag)
{
   int i = 0;
   foreach (StatusFlags val in Enum.GetValues(typeof(StatusFlags)))
   {
      if (flag == val) break;
      i++;
   }
   return i;
}

It is used like this:

StatusFlags f = StatusFlags.Failed_Programming;

// I want the position i.e value of 3 not the value the enum is associated with i.e 4
int Index = f.GetStatusID();

Is there is a better way to do this?

phuclv
  • 37,963
  • 15
  • 156
  • 475
user2425056
  • 327
  • 2
  • 4
  • 14
  • 1
    What would you expect to happen for underlying values which are defined more than once? `enum Foo { X = 0, Y = 0 }`? At that point the values are indistinguishable, in that `Foo.X == Foo.Y`. Also note that `Enum.GetValues()` does *not* return the values in declaration order - it returns them in underlying value magnitude order. See http://msdn.microsoft.com/en-us/library/system.enum.getvalues(v=vs.110).aspx – Jon Skeet Mar 18 '14 at 11:32
  • 1
    Are the enum values powers of 2 throughout? – shree.pat18 Mar 18 '14 at 11:33
  • Also, a return of 0 is ambiguous (not found/first). – Peter - Reinstate Monica Mar 18 '14 at 11:34
  • The values are powers of 2 as the primary purpose of the enum is for bit fields. There will not be duplicated values. The values are declared in increasing order of magnitude so the GetStatusID() should work. will edit the function to remove the ambiguous return. – user2425056 Mar 18 '14 at 11:49
  • If the values are unique, you could use a logarithm base 2 to get the power of 2 for each value. If and only if the values are unique, these will be the same as the 1-based index – stombeur Mar 18 '14 at 11:58
  • Please, do not include information about a language used in a question title unless it wouldn't make sense without it. Tags serve this purpose. – Ondrej Janacek Mar 18 '14 at 11:59

5 Answers5

2

How about using attributes on your enum? Something like this:

[Flags]
public enum StatusFlags
{
    [Index=0]
    None = 0,

    [Index=1]             
    Untested = 1,            

    [Index=2]
    Passed_Programming = 2,

    [Index=3]  
    Failed_Programming = 4,
    // ... many more
}

Then you can the index value of your enum like this:

var type = typeof(StatusFlags);
var statusFlag = type.GetMember(StatusFlags.Untested.ToString());
var attributes = statusFlag [0].GetCustomAttributes(typeof(IndexAttribute),false);
var index = int.Parse(((IndexAttribute)attributes[0]).Index); //if you need an int value
Chris
  • 5,040
  • 3
  • 20
  • 24
  • That's neat too. It's almost like having n (since you could have more attributes) enums in one. That it doesn't guarantee successive or unique indices can be a advantage or disadvantage, depending on the use case. – Peter - Reinstate Monica Mar 18 '14 at 12:17
1

You could do this:

public static int GetStatusID(this StatusFlags flag)
{
    return
        Enum
            .GetValues(typeof(StatusFlags))
            .Cast<StatusFlags>()
            .Select((f, n) => new { f, n })
            .Where(fn => fn.f == flag)
            .Select(fn => fn.n)
            .DefaultIfEmpty(0)
            .First();
}
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • @ReeCube - It doesn't require state and is a pure function. – Enigmativity Mar 18 '14 at 11:43
  • probably slower though – Dreamwalker Mar 18 '14 at 11:48
  • I think this is a case where using LINQ just obscures the meaning of the code. A simple loop is much easier on the eyes... – João Angelo Mar 18 '14 at 11:54
  • @JoãoAngelo - It's in the eye of the beholder. I like this kind of thing much better as I can read the code top to bottom without needing to understand any code control flow. – Enigmativity Mar 18 '14 at 12:00
  • This is a good example of imperative vs functional, though I will use the single line "Array.IndexOf" version for now. – user2425056 Mar 18 '14 at 12:11
  • @user2425056 - My answer is a good example of functional. So is `Array.IndexOf`. My answer doesn't have anything to do with imperative. Are you saying that with respect to your approach? – Enigmativity Mar 18 '14 at 12:16
  • The original function which was iterating over all the possible values until it found a match was what I think is an example of the imperative approach. I know your answer has nothing to do with imperative that is why I said this was a good example of the 2 approaches contrasting with each other. I prefer the functional approach. – user2425056 Mar 18 '14 at 12:24
  • @Enigmativity: Would you mind elaborating briefly on the workings of your linq? Especially, how does the compiler infer type and value of n? To the linq beginner these things always look like magic, even after having read part of Jon's book (hi Jon). – Peter - Reinstate Monica Mar 18 '14 at 12:30
  • 1
    @PeterSchneider - It's really quite simple - the `.Select(f, n) =>` overload populates `n` with the current index for the value `f`. It's just a select with value and index, and not just value like the normal select extension. – Enigmativity Mar 19 '14 at 01:15
  • This is brilliant. Thanks. If you change it from `Where(fn => fn.f == flag)` to `Where(fn => flag.HasFlag(fn.f))`, you can accommodate flagged enums as well. – Chris Pratt Mar 08 '17 at 19:48
1

A deleted answer here suggested something that resembled

public static int GetStatusID(this StatusFlags flag)
{
    return Array.IndexOf(Enum.GetValues(typeof(StatusFlags)), flag);
}

and was just missing the syntactical point that IndexOf is a static function in the Array class, not an extension method. I like it though for brevity.

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
  • Note that IndexOf returns -1 when the flag doesn't exist which changes the OP's default 0 to something more reasonable. The MSDN docs are here http://msdn.microsoft.com/en-us/library/eha9t187%28v=vs.110%29.aspx. – Peter - Reinstate Monica Mar 18 '14 at 11:59
  • Like this for the brevity. Also like the LINQ one though, this could turn out useful. – user2425056 Mar 18 '14 at 12:03
1

How about just using math? He says the flags go up in powers of 2

int GetStatusID(this StatusFlags flag)
{
    if (((int)flag) == 0) return 0;
    return (Math.Log((double)flag) / Math.Log(2D)) + 1;
}
phuclv
  • 37,963
  • 15
  • 156
  • 475
Dreamwalker
  • 3,032
  • 4
  • 30
  • 60
0

If each flag has only 1 bit set like that then the index is just Math.Log2((int)flag) + 1. However Math.Log2 is a floating-point operation and is very slow so don't use it

If you're using .NET Core then there are BitOperations.Log2 and BitOperations.TrailingZeroCount which map directly to hardware instructions like TZCNT/BSF in x86 or CLZ in ARM, hence are much more efficient and the result is like this

public static int GetStatusID(this StatusFlags flag)
{
    if ((int)flag == 0)
        return 0;
    return BitOperations.Log2((int)flag);
    // or return BitOperations.TrailingZeroCount((int)flag) + 1;
}

If you're using an older .NET framework then calculate see the way to calculate integer log2 quickly in these questions

phuclv
  • 37,963
  • 15
  • 156
  • 475