Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What determines which name is selected when calling ToString() on an enum value which has multiple corresponding names?

What determines which name is selected when calling ToString() on an enum value which has multiple corresponding names?

Long explanation of question follows below.

I have determined that this not determined uniquely by any of: alphabetical order; declaration order; nor, name length.

For example, consider that I want to have an enum where the numeric values correspond directly to a practical use, (e.g. rgb values for color).

public enum RgbColor 
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Now, with this enum, calling default(RgbColor) will return the rgb value for black. Let's say I don't want the default value to be black, because I want UI designers to be able to use a call to "Default" when they don't have specific instructions about what color to use. For now, the Default value for UI designers to use is actually "Blue", but that could change. So, I add an additional TextDefault definition on the enum, and now it looks like:

public enum RgbColorWithTextDefaultFirst
{
    TextDefault = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Now, I have tested this and I find that calling RgbColorWithTextDefaultFirst.TextDefault.ToString() and RgbColorWithTextDefaultFirst.Blue.ToString() both yield "Blue". So, I figured that the name that is declared last will overwrite the name of the previous declarations. To test my assumption, I wrote:

public enum RgbColorWithTextDefaultLast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    TextDefault = 0x0000ff 
}

However, to my surprise, RgbColorWithTextDefaultLast.Blue.ToString() and RgbColorWithTextDefaultLast.TextDefault.ToString(). My next guess is that it sorts the names by alphabetical order and returns the first one. To test this I try:

public enum RgbColorWithATextDefaultFirst
{
    ATextDefault = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

public enum RgbColorWithATextDefaultLast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    ATextDefault = 0x0000ff
}

Now, for all four of RgbColorWithATextDefaultFirst.ATextDefault.ToString(), RgbColorWithATextDefaultFirst.Blue.ToString(), RgbColorWithATextDefaultLast.ATextDefault.ToString(), RgbColorWithATextDefaultLast.Blue.ToString(), I end up with "Blue". I realize that there is another distinguishing factor, which is length of the string. My guess is now that the selected name is determined by the length of the name string. So, my test is to use these declarations:

public enum RgbColorWithALast
{
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff,
    A = 0x0000ff
}

public enum RgbColorWithAFirst
{
    A = 0x0000ff,
    Black   = 0x000000,
    Red     = 0xff0000,
    Green   = 0x00ff00,
    Blue    = 0x0000ff,
    White   = 0xffffff
}

Now, guess what value I got for all of: RgbColorWithAFirst.A.ToString(); RgbColorWithAFirst.Blue.ToString(); RgbColorWithALast.A.ToString(), RgbColorWithALast.Blue.ToString(). That's right, "Blue".

At this point, I've given up on trying to figure out what determines this by guessing. I opened up reflector, and I'm going to take a look and try to figure this out, but I figured I would ask a question here to see if anyone here already knows the answer, which is, again: What determines which name is selected when calling ToString() on an enum value which has multiple corresponding names?

like image 698
smartcaveman Avatar asked Oct 03 '12 14:10

smartcaveman


People also ask

What happens when you toString an enum?

ToString(String)Converts the value of this instance to its equivalent string representation using the specified format.

How does enum toString work?

The Java Enum has two methods that retrieve that value of an enum constant, name() and . toString(). The toString() method calls the name() method which returns the string representation of the enum constant. In listing 1, the value returned by calling the name() and toString() on an Animal.

Can we convert enum to string in C#?

We can convert an enum to string by calling the ToString() method of an Enum.


2 Answers

I might be going too far here, but I think it's decided by binary search of the sorted values, and so can depend on the parity of the total number of values. You can illustrate this with your last example (RgbColorWithAFirst and RgbColorWithALast) by defining another value in both - then you get A from all the ToString invocations.

I got here by decompiling mscorlib (4.0) and noting that eventually we get to a call to Array.BinarySearch on a sorted array of the declared values. Naturally, the binary search stops as soon as it gets a match, so to get it to switch between two identical values the easiest way is to alter the search tree, by adding an extra value.

Of course, this is an implementation detail and should not be relied on. It seems to me that in your case you would be best served by using DescriptionAttribute on enum values where you want to be explicit about the display value, and a helper method such as:

public static class EnumExtensions
{
    public static string Description(this Enum value)
    {
        var field = value.GetType().GetField(value.ToString());
        var attribute = Attribute.GetCustomAttribute(
                            field, 
                            typeof (DescriptionAttribute)) 
                        as DescriptionAttribute;

        return attribute == null ? value.ToString() : attribute.Description;
    }
}
like image 180
AakashM Avatar answered Sep 24 '22 06:09

AakashM


The documentation warns that duplicate values will produce errors.
You have duplicate values and are getting errors - not a surprise.

You are getting the first Equal.
Equal is based solely on value.
You have no control over the order in which the enum is evaluated.

As stated in comment an enum expects unique values for the type.

Enumeration Types (C# Programming Guide)

However, you should not do this because the implicit expectation is that an enum variable will only hold one of the values defined by the enum. To assign an arbitrary value to a variable of an enumeration type is to introduce a high risk for errors.

Apparently you have uncovered one of the high risk errors with a duplicate values for type.

I characterize this error as non deterministic as I consider changing the order but same data the same input given nothing in the spec state of implies enum input is order dependent. OP considers different order as different data. With the OP's assumption would not characterize this a non deterministic. Still fair to characterized it as an error caused by duplicate values for type.

Another reference that implies uniqueness is expected

Enum.GetName

The return is string (not string[]).

Equals is based solely on value.

Enum.Equals

GetName is going to match on the first value.
That is my definition of non-deterministic.
How can you know which you have if have if they are considered equal?

Suspect Microsoft does not enforce uniqueness of enum type values due to overhead.

OP expects that Enum explicitly associates A value with A string (like a KVP) when there is no indication of that type of association.
The documentation explicitly warns against making that assumption.
Where does the documentation indicate Enum is implemented as a set of key value pair, class, or strut?
The results and methods indicate an Enum is a string and value type (other than char) with a loose association.

Possible work around.

A Dictionary does not require unique values.

public enum RgbColor : byte
{
    Black,
    Red,
    Green,
    Blue,
    White,
    Default
}

static void Main(string[] args)
{
    Dictionary<RgbColor, Int32> ColorRGB = new Dictionary<RgbColor, int>();
    ColorRGB.Add(RgbColor.Black, 0x000000);
    ColorRGB.Add(RgbColor.Default, 0x0000ff);
    ColorRGB.Add(RgbColor.Blue, 0x0000ff);
    ColorRGB.Add(RgbColor.Green, 0x00ff00);
    ColorRGB.Add(RgbColor.White, 0xffffff);

    Debug.WriteLine(ColorRGB[RgbColor.Blue].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.Default].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.Black].ToString("X6"));
    Debug.WriteLine(ColorRGB[RgbColor.White].ToString("X6"));
like image 32
17 revs Avatar answered Sep 22 '22 06:09

17 revs