Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert primitive type value to enum value, when enum contains elements with the same values?

I wrote the code :

enum FlipRotate2dEnum : byte {
    NO = 0,    None               = 0,
    R2 = 1,    RotateTwice        = 1,
    FX = 2,    FlipX              = 2,
    FY = 3,    FlipY              = 3,
    D1 = 4,    ReflectDiagonal1   = 4,
    D2 = 5,    ReflectDiagonal2   = 5,
    RC = 6,    RotateClockwise    = 6,
    RN = 7,    RotateNonClockwise = 7
}
class EnumTest {
    public static void Main() {
        for(byte i = 0; i < 8; ++i) {
            FlipRotate2dEnum v = (FlipRotate2dEnum)i;
            System.Console.WriteLine("{0} {1}", i, v);
        }
    }
}

and I expect to see in ouput :

only short names

0 NO
1 R2
2 FX
3 FY
4 D1
5 D2
6 RC
7 RN

or only long names

0 None
1 RotateTwice
2 FlipX
3 FlipY
4 ReflectDiagonal1
5 ReflectDiagonal2
6 RotateClockwise
7 RotateNonClockwise

or names that occur first, after sorting in alphabetical order, which in this case coincides with "only short names".

But I did not expect to see what the program showed up :

0 None
1 RotateTwice
2 FlipX
3 FlipY
4 ReflectDiagonal1
5 ReflectDiagonal2
6 RotateClockwise
7 RN

Short name at the end of the output. ¿ Why ?


I tried to rearrange columns in enum :

public enum FlipRotate2dEnum : byte {
    None               = 0, NO = 0,
    RotateTwice        = 1, R2 = 1,
    FlipX              = 2, FX = 2,
    FlipY              = 3, FY = 3,
    ReflectDiagonal1   = 4, D1 = 4,
    ReflectDiagonal2   = 5, D2 = 5,
    RotateClockwise    = 6, RC = 6,
    RotateNonClockwise = 7, RN = 7
}
class EnumTest {
    public static void Main() {
        for(byte i = 0; i < 8; ++i) {
            FlipRotate2dEnum v = (FlipRotate2dEnum)i;
            System.Console.WriteLine("{0} {1}", i, v);
        }
    }
}

and again I got a surprise in the output :

0 NO
1 R2
2 FX
3 FY
4 D1
5 D2
6 RC
7 RotateNonClockwise

¿ Why ? Please, explain to me what's going on here.

like image 754
leofun01 Avatar asked May 11 '18 02:05

leofun01


1 Answers

Enum variable is basically just a number, it doesn't have a label (such as R2) attached to it. For example, FlipRotate2dEnum.RN and FlipRotate2dEnum.RotateNonClockwise are the same thing, because they have the same value 7. Actually if you do:

Console.WriteLine(FlipRotate2dEnum.RotateNonClockwise);

You will see RN as output. As stated in documentation of Enum.ToString()

If multiple enumeration members have the same underlying value and you attempt to retrieve the string representation of an enumeration member's name based on its underlying value, your code should not make any assumptions about which name the method will return

But if you are curious how exactly result you see is obtained internally, please read below.

First, array of underlying values (in your case - bytes) is obtained roughly like this (but not exactly like this):

byte[] values = Enum.GetValues(typeof(FlipRotate2dEnum)).Cast<byte>().ToArray();

It does not contain distinct values, just all values in enum, sorted. So in this case 16 values:

0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7

Then array of names is obtained (again, not exactly like this, but in a similar way). This array is also sorted, by corresponding value (that is - names for values 0 go before names for values 1 etc):

var names = Enum.GetNames(typeof(FlipRotate2dEnum));

It also contains 16 names:

NO, None, R2, RotateTwice, ...

Then, binary search is performed over array of values for the given value. Say we call FlipRotate2dEnum.RotateNonClockwise.ToString(). It has value 7, so binary search is performed for 7:

var index = Array.BinarySearch(values, (byte)7)

Then, resulting index is used to get name:

var name = names[index];

Now, if you know how binary search works - you should now understand why results are not quite what you expect. With binary search values are not explored sequentially in the way they are defined, instead search starts in the middle and reduces search space in a half with every iteration, so it's not very obvious to which name your value is mapped while starring at enum declaration.

Of course you can achieve your desired result by just swapping RN and RotateNonClockwise, but I hope you understand from the above that it's not a great idea, not to mention you would rely on implementation detail, and addind\removing new members will be a pain.

As stated in comments - if you need "display" names for your enums - decorate them with some attribute and use it. Try to avoid enums with multiple names mapping to the same value, but if you do - at least don't think about that as one-to-one mapping between value and name, because that's not how it works.

like image 125
Evk Avatar answered Oct 10 '22 05:10

Evk