How are ambiguous enum values resolved in C#?
Asked Answered
Z

1

32

I checked the section of the C# language specification regarding enums, but was unable to explain the output for the following code:

enum en {
    a = 1, b = 1, c = 1,
    d = 2, e = 2, f = 2,
    g = 3, h = 3, i = 3,
    j = 4, k = 4, l = 4
}

en[] list = new en[] {
    en.a, en.b, en.c,
    en.d, en.e, en.f,
    en.g, en.h, en.i,
    en.j, en.k, en.l
};
foreach (en ele in list) {
    Console.WriteLine("{1}: {0}", (int)ele, ele);
}

It outputs:

c: 1
c: 1
c: 1
d: 2
d: 2
d: 2
g: 3
g: 3
g: 3
k: 4
k: 4
k: 4

Now, why would it select the third "1", the first "2" and "3", but the second "4"? Is this undefined behavior, or am I missing something obvious?

Zoometry answered 20/7, 2014 at 16:38 Comment(0)
A
36

This is specifically documented to be undocumented behaviour.

There is probably something in the way the code is written that will end up picking the same thing every time but the documentation of Enum.ToString states this:

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.

(my emphasis)

As mentioned in a comment, a different .NET runtime might return different values, but the whole problem with undocumented behaviour is that it is prone to change for no (seemingly) good reason. It could change depending on the weather, the time, the mood of the programmer, or even in a hotfix to the .NET runtime. You cannot rely on undocumented behavior.

Note that in your example, ToString is exactly what you want to look at since you're printing the value, which will in turn convert it to a string.

If you try to do a comparison, all the enum values with the same underlying numerical value is equivalent and you cannot tell which one you stored in a variable in the first place.

In other words, if you do this:

var x = en.a;

there is no way to afterwards deduce that you wrote en.a and not en.b or en.c as they all compare equal, they all have the same underlying value. Well, short of creating a program that reads its own source.

Amandy answered 20/7, 2014 at 16:40 Comment(7)
At the very least, "every time" very likely won't apply when you run the program on multiple different implementations/versions of .NET.Knotted
That may be true, but however stable or unstable that piece of code is, it is still undocumented.Amandy
Yes. My issue is not with that, but with your answer: your answer suggests that programs can rely on getting the same enumerator name every time (even though I don't think that's what you meant to say), and the documentation doesn't agree with that suggestion.Knotted
The reference source does tell you why the output is the way it is. The 4.5.1 implementation is to get the enum names and values, sort them, then do a binary search for the desired value. Thus, it is dependent on sorting and searching implementations, which could easily change. The sort is actually an insertion sort because the common use case is a small number of values that are already sorted. The binary search is just Array.BinarySearch.Whitelivered
And a binary search is not guaranteed to find the first or the last value if the value to search for occurs multiple times.Amandy
@LasseV.Karlsen could you add the information from mike z about the internals of why this undefined behaviour actually behaves the way it does? I found it quite enlighteningIntervale
To be honest, I'd rather not. You cannot rely on undocumented behavior and that is my answer. I'd rather not actually document the current behavior. It can stay in a comment.Amandy

© 2022 - 2024 — McMap. All rights reserved.