15

当调用具有多个对应名称的值ToString()时,是什么决定了选择哪个名称?enum

问题的详细解释如下。

我已经确定这不是由以下任何一个唯一确定的:字母顺序;申报单;也不是,名称长度。

例如,考虑我想要一个枚举,其中数值直接对应于实际用途(例如颜色的 rgb 值)。

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

现在,使用这个枚举,调用default(RgbColor)将返回黑色的 rgb 值。假设我不希望默认值是黑色,因为我希望 UI 设计人员能够在他们没有关于使用什么颜色的具体说明时使用对“默认”的调用。目前,UI 设计人员使用的默认值实际上是“蓝色”,但这可能会改变。所以,我在枚举上添加了一个额外的TextDefault定义,现在它看起来像:

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

现在,我已经对此进行了测试,我发现调用RgbColorWithTextDefaultFirst.TextDefault.ToString()并且RgbColorWithTextDefaultFirst.Blue.ToString()两者都产生“蓝色”。因此,我认为最后声明的名称将覆盖先前声明的名称。为了检验我的假设,我写道:

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

然而,令我惊讶的是,RgbColorWithTextDefaultLast.Blue.ToString()RgbColorWithTextDefaultLast.TextDefault.ToString()。我的下一个猜测是它按字母顺序对名称进行排序并返回第一个。为了测试这一点,我尝试:

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
}

现在,对于所有四个RgbColorWithATextDefaultFirst.ATextDefault.ToString(), RgbColorWithATextDefaultFirst.Blue.ToString(), RgbColorWithATextDefaultLast.ATextDefault.ToString(), RgbColorWithATextDefaultLast.Blue.ToString(),我最终得到“蓝色”。我意识到还有另一个区别因素,那就是字符串的长度。我现在的猜测是,所选名称是由名称字符串的长度决定的。所以,我的测试是使用这些声明:

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
}

现在,猜猜我得到的所有值是多少RgbColorWithAFirst.A.ToString()RgbColorWithAFirst.Blue.ToString(); RgbColorWithALast.A.ToString(), RgbColorWithALast.Blue.ToString(). 没错,就是“蓝”。

在这一点上,我已经放弃试图通过猜测来弄清楚是什么决定了这一点。我打开了反射器,我要看看并试图弄清楚这一点,但我想我会在这里问一个问题,看看这里是否有人已经知道答案,再次是:什么决定了哪个名字是调用具有多个对应名称的值ToString()时选择?enum

4

3 回答 3

6

我在这里可能走得太远了,但我认为这是由排序值的二进制搜索决定的,因此可以取决于值总数的奇偶性。您可以通过在两者中定义另一个值来用上一个示例 (RgbColorWithAFirst和) 来说明这一点 - 然后您可以从所有调用中获得。RgbColorWithALastAToString

我通过反编译mscorlib(4.0) 来到这里,并注意到最终我们会调用Array.BinarySearch对已声明值的排序数组的调用。自然地,二分搜索一旦得到匹配就会停止,所以要让它在两个相同的值之间切换,最简单的方法是通过添加一个额外的值来改变搜索树。

当然,这是一个实现细节,不应依赖。在我看来,在您的情况下,最好使用DescriptionAttribute您希望明确显示值的枚举值和辅助方法,例如:

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;
    }
}
于 2012-10-03T15:44:45.853 回答
3

文档警告说重复值会产生错误。
您有重复的值并且出现错误 - 这并不奇怪。

你得到了第一个 Equal。
平等仅基于价值。
您无法控制评估枚举的顺序。

如评论中所述,枚举需要该类型的唯一值。

枚举类型(C# 编程指南)

但是,您不应该这样做,因为隐含的期望是枚举变量将仅保存枚举定义的值之一。将任意值分配给枚举类型的变量会带来很高的错误风险。

显然,您已经发现了类型重复值的高风险错误之一。

我将此错误描述为不确定性,因为我考虑更改顺序但相同的数据相同的输入在规范状态中没有给出任何内容意味着枚举输入是顺序相关的。OP将不同的顺序视为不同的数据。使用 OP 的假设不会将其表征为非确定性。仍然公平地将其描述为由类型重复值引起的错误。

另一个暗示唯一性的参考是预期的

枚举.GetName

返回是字符串(不是字符串 [])。

Equals 完全基于价值。

枚举.Equals

GetName 将匹配第一个值。
这就是我对非确定性的定义。
如果他们被认为是平等的,你怎么知道你有哪些?

怀疑微软不会因为开销而强制枚举类型值的唯一性。

当没有指示该类型的关联时,OP 期望 Enum 将 A 值与 A 字符串(如 KVP)显式关联。
该文档明确警告不要做出这种假设。
文档在哪里表明 Enum 是作为一组键值对、类或支柱实现的?
结果和方法表明 Enum 是具有松散关联的字符串和值类型(char 除外)。

可能的解决方法。

字典不需要唯一值。

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"));
于 2012-10-03T16:50:48.220 回答
1

行为未定义。即使您能够显示某些特定版本的 .NET 的结果是什么,它也可能会改变。请参阅enum.toString文档,其中说:

如果多个枚举成员具有相同的基础值,并且您尝试根据其基础值检索枚举成员名称的字符串表示形式,则您的代码不应对该方法将返回哪个名称做出任何假设。例如,以下枚举定义了两个具有相同基础值的成员 Shade.Gray 和 Shade.Grey。

枚举阴影 { 白色 = 0,灰色 = 1,灰色 = 1,黑色 = 2 }

以下方法调用尝试检索其基础值为 1 的 Shade 枚举成员的名称。该方法可以返回“Gray”或“Grey”,并且您的代码不应对将返回哪个字符串做出任何假设。

字符串 shadeName = ((Shade) 1).ToString();

另请参阅上一段是文档,其中显示了如何检索所有名称

于 2012-10-03T18:06:18.950 回答