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

倖福魔咒の 提交于 2021-02-06 15:19:18

问题


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?


回答1:


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;
    }
}



回答2:


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"));



回答3:


The behavior is undefined. Even if you are able to show what the result is for some specific version of .NEt it could change. See the enum.toString documentation, which says:

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. For example, the following enumeration defines two members, Shade.Gray and Shade.Grey, that have the same underlying value.

enum Shade { White = 0, Gray = 1, Grey = 1, Black = 2 }

The following method call attempts to retrieve the name of a member of the Shade enumeration whose underlying value is 1. The method can return either "Gray" or "Grey", and your code should not make any assumptions about which string will be returned.

string shadeName = ((Shade) 1).ToString();

Also see the previous paragraph is documentation, which shows how to retrieve all names



来源:https://stackoverflow.com/questions/12711059/what-determines-which-name-is-selected-when-calling-tostring-on-an-enum-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!