Home » c# » Which Enum constant will I get if the Enum values are same

Which Enum constant will I get if the Enum values are same

Posted by: admin November 30, 2017 Leave a comment

Questions:

Is there a logic to which constant I get if there are more than one enum constant that has the same value?

I tried the variations below, but couldn’t get a reasonable logic.

Main Method:

public class Program
{
    public static void Main(string[] args)
    {
        Test a = 0;
        Console.WriteLine(a);
    }
}

First try:

enum Test
{
    a1=0,
    a2=0,
    a3=0,
    a4=0,
}

Output:

a2

Second try:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4=0,
}

Output:

a4

Third try:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4,
}

Output:

a2

Fourth try:

enum Test
{
    a1=0,
    a2=0,
    a3,
    a4
}

Output:

a1
Answers:

The documentation actually addresses 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.

(emphasis added)

However, this does not mean that the result is random. What that means it that it’s an implementation detail that is subject to change. The implementation could completely change with just a patch, could be different across compilers (MONO, Roslyn, etc.), and be different on different platforms.

If your system is designed that it requires that the reverse-lookup for enums is consistent over time and platforms, then don’t use Enum.ToString. Either change your design so it’s not dependent on that detail, or write your own method that will be consistent.

So you should not write code that depends on that implementation, or you are taking a risk that it will change without your knowledge in a future release.

Questions:
Answers:

TL;DR: All fields of an enum will be extract by reflection, then insertion sorted and binary searched for the first matching value.


The call chain looked this :

Enum.Tostring();
Enum.InternalFormat(RuntimeType eT, Object value);
Enum.GetName(Type enumType, Object value);
Type.GetEnumName(object value);

Type.GetEnumName(object value) is implemented as such :

    public virtual string GetEnumName(object value)
    {
        // standard argument guards...

        Array values = GetEnumRawConstantValues();
        int index = BinarySearch(values, value);

        if (index >= 0)
        {
            string[] names = GetEnumNames();
            return names[index];
        }

        return null;
    }

Both GetEnumRawConstantValues() and GetEnumNames() rely on GetEnumData(out string[] enumNames, out Array enumValues) :

    private void GetEnumData(out string[] enumNames, out Array enumValues)
    {
        Contract.Ensures(Contract.ValueAtReturn<String[]>(out enumNames) != null);
        Contract.Ensures(Contract.ValueAtReturn<Array>(out enumValues) != null);

        FieldInfo[] flds = GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);

        object[] values = new object[flds.Length];
        string[] names = new string[flds.Length];

        for (int i = 0; i < flds.Length; i++)
        {
            names[i] = flds[i].Name;
            values[i] = flds[i].GetRawConstantValue();
        }

        // Insertion Sort these values in ascending order.
        // We use this O(n^2) algorithm, but it turns out that most of the time the elements are already in sorted order and
        // the common case performance will be faster than quick sorting this.
        IComparer comparer = Comparer.Default;
        for (int i = 1; i < values.Length; i++)
        {
            int j = i;
            string tempStr = names[i];
            object val = values[i];
            bool exchanged = false;

            // Since the elements are sorted we only need to do one comparision, we keep the check for j inside the loop.
            while (comparer.Compare(values[j - 1], val) > 0)
            {
                names[j] = names[j - 1];
                values[j] = values[j - 1];
                j--;
                exchanged = true;
                if (j == 0)
                    break;
            }

            if (exchanged)
            {
                names[j] = tempStr;
                values[j] = val;
            }
        }

        enumNames = names;
        enumValues = values;
    }

When followed, GetFields(BindingFlags bindingAttr) leads to an abstract method, but searching “GetFields” on msdn will yield you EnumBuilder.GetFields(BindingFlags bindingAttr). And if we follow its call chain :

EnumBuilder.GetFields(BindingFlags bindingAttr);
TypeBuilder.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFields(BindingFlags bindingAttr);
RuntimeType.GetFieldCandidates(String name, BindingFlags bindingAttr, bool allowPrefixLookup);
RuntimeTypeCache.GetFieldList(MemberListType listType, string name);
RuntimeTypeCache.GetMemberList<RuntimeFieldInfo>(ref MemberInfoCache<T> m_cache, MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetMemberList(MemberListType listType, string name, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.Populate(string name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.GetListByName(char* pName, int cNameLen, byte* pUtf8Name, int cUtf8Name, MemberListType listType, CacheType cacheType);
MemberInfoCache<RuntimeFieldInfo>.PopulateFields(Filter filter);
// and from here, it is a wild ride...

So, I will quote Type.GetFields remarks :

The GetFields method does not return fields in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which fields are returned, because that order varies.