Last week I posted a question about Enums, which contained the following code:
1 enum MyColor
2 {
3 White,
4 Red = 0,
5 Green = 1,
6 Blue = 1,
7 }
8
9 static void Main(string[] args)
10 {
11 Console.WriteLine(MyColor.White);
12 Console.WriteLine(MyColor.Red);
13 Console.WriteLine(MyColor.Green);
14 Console.WriteLine(MyColor.Blue);
15 }
There are two things you should have noticed about this code:
1) The values in the enum contain duplicates: White and Red both are 0, and Green and Blue both are 1.
2) The Console.WriteLine statements execute a ToString-method on the enums, which prints the text of the enum instead of its value.
If you thought a ToString on an enum outputs the value, the answer would be "0, 0, 1, 1". You can check this by modifying the "Console.WriteLine(MyColor...)" to "Console.WriteLine((int)MyColor...)".
If you knew the ToString outputs the name of the enum, the answer is a bit more of a guess. If you combined the "0, 0, 1, 1" with the enum-names, you would expect the answer to be "White, White, Green, Green", because the "WriteLine(MyColor.Red)" is going to write the same enum-value as "WriteLine(MyColor.White)" and White is the first value in the enum. But this isn't the correct answer:

What happened here? I'll explain in a moment, but first let me show you the result can get stranger by changing the code a tiny bit. Let's add one more name (Black) to the MyColor-enumeration:
1 enum MyColor
2 {
3 White,
4 Red = 0,
5 Green = 1,
6 Blue = 1,
7 Black = 2
8 }
9
10 static void Main(string[] args)
11 {
12 Console.WriteLine(MyColor.White);
13 Console.WriteLine(MyColor.Red);
14 Console.WriteLine(MyColor.Green);
15 Console.WriteLine(MyColor.Blue);
16 }
The rest of the code hasn't changed. You would expect the output to be the same as before, right?! Well it's not:

What happened here? At first I played around with the enum and the Console.WriteLine statement to see if I could figure out what was happening. But this didn't help me much. So I started Reflector and looked at the Enum.ToString-method:
1 public override string ToString()
2 {
3 Type type = base.GetType();
4 object obj2 = ((RtFieldInfo)GetValueField(type)).InternalGetValue(this, false);
5 return InternalFormat(type, obj2);
6 }
It calls the InternalFormat-method to do most of the work:
1 private static string InternalFormat(Type eT, object value)
2 {
3 if (eT.IsDefined(typeof(FlagsAttribute), false))
4 {
5 return InternalFlagsFormat(eT, value);
6 }
7 string valueAsString = InternalGetValueAsString(eT, value);
8 if (valueAsString == null)
9 {
10 return value.ToString();
11 }
12 return valueAsString;
13 }
This method in turn calls InternalGetValueAsString:
1 private static string InternalGetValueAsString(Type enumType, object value)
2 {
3 HashEntry hashEntry = GetHashEntry(enumType);
4 Type underlyingType = GetUnderlyingType(enumType);
5 if ((((underlyingType == intType)
6 || (underlyingType == typeof(short)))
7 || ((underlyingType == typeof(long))
8 || (underlyingType == typeof(ushort))))
9 || (((underlyingType == typeof(byte))
10 || (underlyingType == typeof(sbyte)))
11 || ((underlyingType == typeof(uint))
12 || (underlyingType == typeof(ulong)))))
13 {
14 ulong num = ToUInt64(value);
15 int index = BinarySearch(hashEntry.values, num);
16 if (index >= 0)
17 {
18 return hashEntry.names[index];
19 }
20 }
21 return null;
22 }
And this is where things start to get interesting. See the call to BinarySearch on line 15? This method is also implemented in the Enum-class:
1 private static int BinarySearch(ulong[] array, ulong value)
2 {
3 int num = 0;
4 int num2 = array.Length - 1;
5 while (num <= num2)
6 {
7 int index = (num + num2) >> 1;
8 ulong num4 = array[index];
9 if (value == num4)
10 {
11 return index;
12 }
13 if (num4 < value)
14 {
15 num = index + 1;
16 }
17 else
18 {
19 num2 = index - 1;
20 }
21 }
22 return ~num;
23 }
It provides a very simple implementation of an BinarySearch-algorithm. All this algorithm does is: take an ascending sorted array, look at the value in the middle and compare that to the item you're looking for. If the value you are looking for is smaller, repeat the same process for the first part of the array, if the value is higher repeat the same process for the second part of the array. In the end you will either have found your item, or you can say the item isn't in the array.
Now, lets go back to our enumeration MyColor. If you put all values into an array, you will get "0, 0, 1, 1, 2". If you use the BinarySearch-algorithm to find the value 1, you would get the first "1" in the array (Green), because it's in the middle. If you search for the value "0", you would end up with the first "0" in the array (White), because the first run of the BinarySearch determines the value you are looking for is in "0, 0", because "0" is smaller than the middle-value "1". Line 7 ("int index = (num + num2) >> 1;") in the BinarySearch then translates to 0 ((0+1)>>1 == 0), so the first "0" and it's corresponding name "White" is returned.
Now apply this knowledge to the enumeration at the top of this post, containing 4 values: "0, 0, 1, 1". If you search for "1", you will get the first "1" (Green), because "(0 + 3) >> 1" = 1, and "(2 + 3) >> 1" = 2 (look at line 7 of the BinarySearch algoritme again if I'm going to fast here). Now if you were to search for the value "0" in the same array, you would get the second "0" (Red), because "(0 + 3) >> 1" = 1, and this index immediately compares to the value we are looking for!
So the number of elements in the enumeration determines the name returned by Enum.ToString if there are duplicate values in the enumeration!
My advise, don't use duplicate values in your enumerations! Next week I'll post a custom Code Analysis-rule I've build to check this.